/** * 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 <http://www.gnu.org/licenses/>. */ 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.xa.XAException; import javax.transaction.xa.XAResource; import org.neo4j.graphdb.GraphDatabaseService; import org.neo4j.graphdb.Node; import org.neo4j.graphdb.Relationship; import org.neo4j.graphdb.RelationshipType; import org.neo4j.helpers.Pair; import org.neo4j.kernel.impl.core.LockReleaser; import org.neo4j.kernel.impl.core.PropertyIndex; import org.neo4j.kernel.impl.nioneo.store.DynamicRecord; import org.neo4j.kernel.impl.nioneo.store.InvalidRecordException; import org.neo4j.kernel.impl.nioneo.store.NeoStore; 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.PropertyIndexData; 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.RelationshipTypeData; import org.neo4j.kernel.impl.nioneo.store.RelationshipTypeRecord; import org.neo4j.kernel.impl.nioneo.store.RelationshipTypeStore; import org.neo4j.kernel.impl.nioneo.xa.Command.PropertyCommand; import org.neo4j.kernel.impl.persistence.NeoStoreTransaction; import org.neo4j.kernel.impl.transaction.LockManager; import org.neo4j.kernel.impl.transaction.LockType; 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<Long,NodeRecord> nodeRecords = new HashMap<Long,NodeRecord>(); private final Map<Long, PropertyRecord> propertyRecords = new HashMap<Long, PropertyRecord>(); private final Map<Long,RelationshipRecord> relRecords = new HashMap<Long,RelationshipRecord>(); private final Map<Integer,RelationshipTypeRecord> relTypeRecords = new HashMap<Integer,RelationshipTypeRecord>(); private final Map<Integer,PropertyIndexRecord> propIndexRecords = new HashMap<Integer,PropertyIndexRecord>(); private final ArrayList<Command.NodeCommand> nodeCommands = new ArrayList<Command.NodeCommand>(); private final ArrayList<Command.PropertyCommand> propCommands = new ArrayList<Command.PropertyCommand>(); private final ArrayList<Command.PropertyIndexCommand> propIndexCommands = new ArrayList<Command.PropertyIndexCommand>(); private final ArrayList<Command.RelationshipCommand> relCommands = new ArrayList<Command.RelationshipCommand>(); private final ArrayList<Command.RelationshipTypeCommand> relTypeCommands = new ArrayList<Command.RelationshipTypeCommand>(); private final NeoStore neoStore; private boolean committed = false; private boolean prepared = false; private final LockReleaser lockReleaser; private final LockManager lockManager; private XaConnection xaConnection; WriteTransaction( int identifier, XaLogicalLog log, NeoStore neoStore, LockReleaser lockReleaser, LockManager lockManager ) { super( identifier, log ); this.neoStore = neoStore; this.lockReleaser = lockReleaser; this.lockManager = lockManager; } @Override public boolean isReadOnly() { if ( isRecovered() ) { if ( nodeCommands.size() == 0 && propCommands.size() == 0 && relCommands.size() == 0 && relTypeCommands.size() == 0 && propIndexCommands.size() == 0 ) { return true; } return false; } if ( nodeRecords.size() == 0 && relRecords.size() == 0 && relTypeRecords.size() == 0 && propertyRecords.size() == 0 && propIndexRecords.size() == 0 ) { return true; } return false; } @Override public void doAddCommand( XaCommand command ) { // override } @Override protected void doPrepare() throws XAException { if ( committed ) { throw new XAException( "Cannot prepare committed transaction[" + getIdentifier() + "]" ); } if ( prepared ) { throw new XAException( "Cannot prepare prepared transaction[" + getIdentifier() + "]" ); } // generate records then write to logical log via addCommand method prepared = true; for ( RelationshipTypeRecord record : relTypeRecords.values() ) { Command.RelationshipTypeCommand command = new Command.RelationshipTypeCommand( neoStore.getRelationshipTypeStore(), record ); relTypeCommands.add( command ); addCommand( command ); } for ( NodeRecord record : nodeRecords.values() ) { if ( !record.inUse() && record.getNextRel() != Record.NO_NEXT_RELATIONSHIP.intValue() ) { throw new InvalidRecordException( "Node record " + record + " still has relationships" ); } Command.NodeCommand command = new Command.NodeCommand( neoStore.getNodeStore(), record ); nodeCommands.add( command ); if ( !record.inUse() ) { removeNodeFromCache( record.getId() ); } addCommand( command ); } for ( RelationshipRecord record : relRecords.values() ) { Command.RelationshipCommand command = new Command.RelationshipCommand( neoStore.getRelationshipStore(), record ); relCommands.add( command ); if ( !record.inUse() ) { removeRelationshipFromCache( record.getId() ); } addCommand( command ); } for ( PropertyIndexRecord record : propIndexRecords.values() ) { Command.PropertyIndexCommand command = new Command.PropertyIndexCommand( neoStore.getPropertyStore().getIndexStore(), record ); propIndexCommands.add( command ); addCommand( command ); } for ( PropertyRecord record : propertyRecords.values() ) { Command.PropertyCommand command = new Command.PropertyCommand( neoStore.getPropertyStore(), record ); propCommands.add( command ); addCommand( command ); } } @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 ) { propIndexCommands.add( (Command.PropertyIndexCommand) xaCommand ); } else if ( xaCommand instanceof Command.RelationshipTypeCommand ) { relTypeCommands.add( (Command.RelationshipTypeCommand) 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 { for ( RelationshipTypeRecord record : relTypeRecords.values() ) { if ( record.isCreated() ) { getRelationshipTypeStore().freeId( record.getId() ); for ( DynamicRecord dynamicRecord : record.getTypeRecords() ) { if ( dynamicRecord.isCreated() ) { getRelationshipTypeStore().freeBlockId( (int) dynamicRecord.getId() ); } } } removeRelationshipTypeFromCache( record.getId() ); } for ( NodeRecord record : nodeRecords.values() ) { if ( record.isCreated() ) { getNodeStore().freeId( record.getId() ); } removeNodeFromCache( record.getId() ); } for ( RelationshipRecord record : relRecords.values() ) { if ( record.isCreated() ) { getRelationshipStore().freeId( record.getId() ); } removeRelationshipFromCache( record.getId() ); } for ( PropertyIndexRecord record : propIndexRecords.values() ) { if ( record.isCreated() ) { getPropertyStore().getIndexStore().freeId( record.getId() ); for ( DynamicRecord dynamicRecord : record.getKeyRecords() ) { 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() ) { 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 { nodeRecords.clear(); propertyRecords.clear(); relRecords.clear(); relTypeRecords.clear(); propIndexRecords.clear(); nodeCommands.clear(); propCommands.clear(); propIndexCommands.clear(); relCommands.clear(); relTypeCommands.clear(); } } private void removeRelationshipTypeFromCache( int id ) { lockReleaser.removeRelationshipTypeFromCache( id ); } private void removeRelationshipFromCache( long id ) { lockReleaser.removeRelationshipFromCache( id ); } private void removeNodeFromCache( long id ) { lockReleaser.removeNodeFromCache( id ); } private void addRelationshipType( int id ) { setRecovered(); RelationshipTypeData type; if ( isRecovered() ) { type = neoStore.getRelationshipTypeStore().getRelationshipType( id, true ); } else { type = neoStore.getRelationshipTypeStore().getRelationshipType( id ); } lockReleaser.addRelationshipType( type ); } private void addPropertyIndexCommand( int id ) { PropertyIndexData index; if ( isRecovered() ) { index = neoStore.getPropertyStore().getIndexStore().getPropertyIndex( id, true ); } else { index = neoStore.getPropertyStore().getIndexStore().getPropertyIndex( id ); } lockReleaser.addPropertyIndex( index ); } @Override public void doCommit() throws XAException { if ( !isRecovered() && !prepared ) { throw new XAException( "Cannot commit non prepared transaction[" + getIdentifier() + "]" ); } if ( isRecovered() ) { commitRecovered(); return; } if ( !isRecovered() && getCommitTxId() != neoStore.getLastCommittedTx() + 1 ) { throw new RuntimeException( "Tx id: " + getCommitTxId() + " not next transaction (" + neoStore.getLastCommittedTx() + ")" ); } try { committed = true; CommandSorter sorter = new CommandSorter(); // reltypes java.util.Collections.sort( relTypeCommands, sorter ); for ( Command.RelationshipTypeCommand command : relTypeCommands ) { command.execute(); } // property keys java.util.Collections.sort( propIndexCommands, sorter ); for ( Command.PropertyIndexCommand command : propIndexCommands ) { command.execute(); } // primitives java.util.Collections.sort( nodeCommands, sorter ); java.util.Collections.sort( relCommands, sorter ); java.util.Collections.sort( propCommands, sorter ); executeCreated( propCommands, relCommands, nodeCommands ); executeModified( propCommands, relCommands, nodeCommands ); executeDeleted( propCommands, relCommands, nodeCommands ); lockReleaser.commitCows(); neoStore.setLastCommittedTx( getCommitTxId() ); } finally { nodeRecords.clear(); propertyRecords.clear(); relRecords.clear(); relTypeRecords.clear(); propIndexRecords.clear(); nodeCommands.clear(); propCommands.clear(); propIndexCommands.clear(); relCommands.clear(); relTypeCommands.clear(); } } private static void executeCreated( ArrayList<? extends Command>... commands ) { for ( ArrayList<? extends Command> c : commands ) for ( Command command : c ) { if ( command.isCreated() && !command.isDeleted() ) { command.execute(); } } } private static void executeModified( ArrayList<? extends Command>... commands ) { for ( ArrayList<? extends Command> c : commands ) for ( Command command : c ) { if ( !command.isCreated() && !command.isDeleted() ) { command.execute(); } } } private static void executeDeleted( ArrayList<? extends Command>... commands ) { for ( ArrayList<? extends Command> c : commands ) for ( Command command : c ) { if ( command.isDeleted() ) { command.execute(); } } } private void commitRecovered() { try { committed = true; CommandSorter sorter = new CommandSorter(); // property index java.util.Collections.sort( propIndexCommands, sorter ); for ( Command.PropertyIndexCommand command : propIndexCommands ) { command.execute(); addPropertyIndexCommand( (int) command.getKey() ); } // properties java.util.Collections.sort( propCommands, sorter ); for ( Command.PropertyCommand command : propCommands ) { command.execute(); removePropertyFromCache( command ); } // reltypes java.util.Collections.sort( relTypeCommands, sorter ); for ( Command.RelationshipTypeCommand command : relTypeCommands ) { command.execute(); addRelationshipType( (int) command.getKey() ); } // relationships java.util.Collections.sort( relCommands, sorter ); for ( Command.RelationshipCommand command : relCommands ) { command.execute(); removeRelationshipFromCache( command.getKey() ); if ( true /* doesn't work: command.isRemove(), the log doesn't contain the nodes */) { removeNodeFromCache( command.getFirstNode() ); removeNodeFromCache( command.getSecondNode() ); } } // nodes java.util.Collections.sort( nodeCommands, sorter ); for ( Command.NodeCommand command : nodeCommands ) { command.execute(); removeNodeFromCache( command.getKey() ); } neoStore.setRecoveredStatus( true ); try { neoStore.setLastCommittedTx( getCommitTxId() ); } finally { neoStore.setRecoveredStatus( false ); } neoStore.getIdGeneratorFactory().updateIdGenerators( neoStore ); } finally { nodeRecords.clear(); propertyRecords.clear(); relRecords.clear(); relTypeRecords.clear(); propIndexRecords.clear(); nodeCommands.clear(); propCommands.clear(); propIndexCommands.clear(); relCommands.clear(); relTypeCommands.clear(); } } private void removePropertyFromCache( PropertyCommand command ) { long nodeId = command.getNodeId(); long relId = command.getRelId(); if ( nodeId != -1 ) { removeNodeFromCache( nodeId ); } else if ( relId != -1 ) { removeRelationshipFromCache( relId ); } // else means record value did not change } 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 boolean nodeLoadLight( long nodeId ) { NodeRecord nodeRecord = getNodeRecord( nodeId ); if ( nodeRecord != null ) { return true; } 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<Integer,PropertyData> 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 ); long nextProp = nodeRecord.getNextProp(); ArrayMap<Integer, PropertyData> propertyMap = getAndDeletePropertyChain( nextProp ); return propertyMap; } @Override public ArrayMap<Integer,PropertyData> 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." ); } long nextProp = record.getNextProp(); ArrayMap<Integer, PropertyData> propertyMap = getAndDeletePropertyChain( nextProp ); disconnectRelationship( record ); updateNodes( record ); record.setInUse( false ); return propertyMap; } private ArrayMap<Integer, PropertyData> getAndDeletePropertyChain( long startingAt ) { ArrayMap<Integer, PropertyData> result = new ArrayMap<Integer, PropertyData>( 9, false, true ); long nextProp = startingAt; 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 propRecord = getPropertyStore().getRecord( propRecord.getId() ); } 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(); // 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 ) { lockManager.getWriteLock( lockableRel ); lockReleaser.addLockToTransaction( lockableRel, LockType.WRITE ); } 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(); } public Pair<Map<DirectionWrapper, Iterable<RelationshipRecord>>, Long> getMoreRelationships( long nodeId, long position ) { return ReadTransaction.getMoreRelationships( nodeId, position, getRelGrabSize(), getRelationshipStore() ); } private void updateNodes( RelationshipRecord rel ) { if ( rel.getFirstPrevRel() == Record.NO_PREV_RELATIONSHIP.intValue() ) { NodeRecord firstNode = getNodeRecord( rel.getFirstNode() ); if ( firstNode == null ) { firstNode = getNodeStore().getRecord( rel.getFirstNode() ); addNodeRecord( firstNode ); } firstNode.setNextRel( rel.getFirstNextRel() ); } if ( rel.getSecondPrevRel() == Record.NO_PREV_RELATIONSHIP.intValue() ) { NodeRecord secondNode = getNodeRecord( rel.getSecondNode() ); if ( secondNode == null ) { secondNode = getNodeStore().getRecord( rel.getSecondNode() ); addNodeRecord( secondNode ); } secondNode.setNextRel( rel.getSecondNextRel() ); } } @Override public void relRemoveProperty( long relId, PropertyData propertyData ) { long propertyId = propertyData.getId(); RelationshipRecord relRecord = getRelationshipRecord( relId ); if ( relRecord == null ) { relRecord = getRelationshipStore().getRecord( relId ); } if ( !relRecord.inUse() ) { throw new IllegalStateException( "Property remove on relationship[" + relId + "] illegal since it has been deleted." ); } assert assertPropertyChain( relRecord ); PropertyRecord propRecord = getPropertyRecord( propertyId, false, true ); if ( !propRecord.inUse() ) { throw new IllegalStateException( "Unable to delete property[" + propertyId + "] since it is already deleted." ); } propRecord.setRelId( relId ); PropertyBlock block = propRecord.removePropertyBlock( propertyData.getIndex() ); if ( block == null ) { throw new IllegalStateException( "Property with index[" + propertyData.getIndex() + "] is not present in property[" + propertyId + "]" ); } if ( block.isLight() ) { getPropertyStore().makeHeavy( block ); } // TODO: update count on property index record for ( DynamicRecord valueRecord : block.getValueRecords() ) { assert valueRecord.inUse(); valueRecord.setInUse( false, block.getType().intValue() ); propRecord.addDeletedRecord( valueRecord ); } if ( propRecord.size() > 0 ) { propRecord.setChanged(); assert assertPropertyChain( relRecord ); return; } else { if ( unlinkPropertyRecord( propRecord, relRecord ) ) { addRelationshipRecord( relRecord ); } } } @Override public ArrayMap<Integer,PropertyData> relLoadProperties( long relId, boolean light ) { RelationshipRecord relRecord = getRelationshipRecord( relId ); if ( relRecord != null && relRecord.isCreated() ) { return null; } if ( relRecord != null ) { if ( !relRecord.inUse() && !light ) { throw new IllegalStateException( "Relationship[" + relId + "] has been deleted in this tx" ); } } relRecord = getRelationshipStore().getRecord( relId ); if ( !relRecord.inUse() ) { throw new InvalidRecordException( "Relationship[" + relId + "] not in use" ); } return ReadTransaction.loadProperties( getPropertyStore(), relRecord.getNextProp() ); } @Override public ArrayMap<Integer,PropertyData> nodeLoadProperties( long nodeId, boolean light ) { NodeRecord nodeRecord = getNodeRecord( nodeId ); if ( nodeRecord != null && nodeRecord.isCreated() ) { return null; } if ( nodeRecord != null ) { if ( !nodeRecord.inUse() && !light ) { throw new IllegalStateException( "Node[" + nodeId + "] has been deleted in this tx" ); } } nodeRecord = getNodeStore().getRecord( nodeId ); if ( !nodeRecord.inUse() ) { throw new InvalidRecordException( "Node[" + nodeId + "] not in use" ); } return ReadTransaction.loadProperties( getPropertyStore(), nodeRecord.getNextProp() ); } public Object propertyGetValueOrNull( PropertyBlock block ) { return block.getType().getValue( block, block.isLight() ? null : getPropertyStore() ); } @Override public Object loadPropertyValue( PropertyData propertyData ) { PropertyRecord propertyRecord = propertyRecords.get( propertyData.getId() ); if ( propertyRecord == null ) { propertyRecord = getPropertyStore().getRecord( propertyData.getId() ); } PropertyBlock block = propertyRecord.getPropertyBlock( propertyData.getIndex() ); if ( block == null ) { throw new IllegalStateException( "Property with index[" + propertyData.getIndex() + "] is not present in property[" + propertyData.getId() + "]" ); } if ( block.isLight() ) { getPropertyStore().makeHeavy( block ); } return block.getType().getValue( block, getPropertyStore() ); } @Override public void nodeRemoveProperty( long nodeId, PropertyData propertyData ) { long propertyId = propertyData.getId(); NodeRecord nodeRecord = getNodeRecord( nodeId ); if ( nodeRecord == null ) { nodeRecord = getNodeStore().getRecord( nodeId ); addNodeRecord( nodeRecord ); } if ( !nodeRecord.inUse() ) { throw new IllegalStateException( "Property remove on node[" + nodeId + "] illegal since it has been deleted." ); } assert assertPropertyChain( nodeRecord ); PropertyRecord propRecord = getPropertyRecord( propertyId, false, true ); if ( !propRecord.inUse() ) { throw new IllegalStateException( "Unable to delete property[" + propertyId + "] since it is already deleted." ); } propRecord.setNodeId( nodeId ); PropertyBlock block = propRecord.removePropertyBlock( propertyData.getIndex() ); if ( block == null ) { throw new IllegalStateException( "Property with index[" + propertyData.getIndex() + "] is not present in property[" + propertyId + "]" ); } if ( block.isLight() ) { getPropertyStore().makeHeavy( block ); } for ( DynamicRecord valueRecord : block.getValueRecords() ) { assert valueRecord.inUse(); valueRecord.setInUse( false, block.getType().intValue() ); propRecord.addDeletedRecord( valueRecord ); } // propRecord.removeBlock( propertyData.getIndex() ); if (propRecord.size() > 0) { /* * There are remaining blocks in the record. We do not unlink yet. */ propRecord.setChanged(); assert assertPropertyChain( nodeRecord ); return; } else { if ( unlinkPropertyRecord( propRecord, nodeRecord ) ) { addNodeRecord( nodeRecord ); } } } private boolean unlinkPropertyRecord( PropertyRecord propRecord, PrimitiveRecord primitive ) { assert assertPropertyChain( primitive ); assert propRecord.size() == 0; boolean primitiveChanged = false; long prevProp = propRecord.getPrevProp(); long nextProp = propRecord.getNextProp(); if ( primitive.getNextProp() == propRecord.getId() ) { assert propRecord.getPrevProp() == Record.NO_PREVIOUS_PROPERTY.intValue() : propRecord + " for " + primitive; primitive.setNextProp( nextProp ); primitiveChanged = true; } if ( prevProp != Record.NO_PREVIOUS_PROPERTY.intValue() ) { PropertyRecord prevPropRecord = getPropertyRecord( prevProp, true, true ); assert prevPropRecord.inUse() : prevPropRecord + "->" + propRecord + " for " + primitive; prevPropRecord.setNextProp( nextProp ); prevPropRecord.setChanged(); } if ( nextProp != Record.NO_NEXT_PROPERTY.intValue() ) { PropertyRecord nextPropRecord = getPropertyRecord( nextProp, true, true ); assert nextPropRecord.inUse() : propRecord + "->" + nextPropRecord + " for " + primitive; nextPropRecord.setPrevProp( prevProp ); nextPropRecord.setChanged(); } propRecord.setInUse( false ); /* * The following two are not needed - the above line does all the work (PropertyStore * does not write out the prev/next for !inUse records). It is nice to set this * however to check for consistency when assertPropertyChain(). */ propRecord.setPrevProp( Record.NO_PREVIOUS_PROPERTY.intValue() ); propRecord.setNextProp( Record.NO_NEXT_PROPERTY.intValue() ); assert assertPropertyChain( primitive ); return primitiveChanged; } @Override public PropertyData relChangeProperty( long relId, PropertyData propertyData, Object value ) { RelationshipRecord relRecord = getRelationshipRecord( relId ); if ( relRecord == null ) { relRecord = getRelationshipStore().getRecord( relId ); } if ( !relRecord.inUse() ) { throw new IllegalStateException( "Property change on relationship[" + relId + "] illegal since it has been deleted." ); } return primitiveChangeProperty( relRecord, propertyData, value, false ); } @Override public PropertyData nodeChangeProperty( long nodeId, PropertyData propertyData, Object value ) { NodeRecord nodeRecord = getNodeRecord( nodeId ); if ( nodeRecord == null ) { nodeRecord = getNodeStore().getRecord( nodeId ); } if ( !nodeRecord.inUse() ) { throw new IllegalStateException( "Property change on node[" + nodeId + "] illegal since it has been deleted." ); } return primitiveChangeProperty( nodeRecord, propertyData, value, true ); } private PropertyData primitiveChangeProperty( PrimitiveRecord primitive, PropertyData propertyData, Object value, boolean isNode ) { assert assertPropertyChain( primitive ); long propertyId = propertyData.getId(); PropertyRecord propertyRecord = getPropertyRecord( propertyId, true, true ); if ( !propertyRecord.inUse() ) { throw new IllegalStateException( "Unable to change property[" + propertyId + "] since it has been deleted." ); } if ( isNode ) { propertyRecord.setNodeId( primitive.getId() ); } else { propertyRecord.setRelId( primitive.getId() ); } PropertyBlock block = propertyRecord.getPropertyBlock( propertyData.getIndex() ); if ( block == null ) { throw new IllegalStateException( "Property with index[" + propertyData.getIndex() + "] is not present in property[" + propertyId + "]" ); } if ( block.isLight() ) { getPropertyStore().makeHeavy( block ); } propertyRecord.setChanged(); for ( DynamicRecord record : block.getValueRecords() ) { assert record.inUse(); record.setInUse( false, block.getType().intValue() ); propertyRecord.addDeletedRecord( record ); } getPropertyStore().encodeValue( block, propertyData.getIndex(), value ); if ( propertyRecord.size() > PropertyType.getPayloadSize() ) { propertyRecord.removePropertyBlock( propertyData.getIndex() ); /* * The record should never, ever be above max size. Less obviously, it should * never remain empty. If removing a property because it won't fit when changing * it leaves the record empty it means that this block was the last one which * means that it doesn't fit in an empty record. Where i come from, we call this * weird. * assert propertyRecord.size() <= PropertyType.getPayloadSize() : propertyRecord; assert propertyRecord.size() > 0 : propertyRecord; */ propertyRecord = addPropertyBlockToPrimitive( block, primitive, isNode ); } assert assertPropertyChain( primitive ); return block.newPropertyData( propertyRecord, value ); } @Override public PropertyData relAddProperty( long relId, PropertyIndex index, Object value ) { RelationshipRecord relRecord = getRelationshipRecord( relId ); if ( relRecord == null ) { relRecord = getRelationshipStore().getRecord( relId ); addRelationshipRecord( relRecord ); } if ( !relRecord.inUse() ) { throw new IllegalStateException( "Property add on relationship[" + relId + "] illegal since it has been deleted." ); } assert assertPropertyChain( relRecord ); PropertyBlock block = new PropertyBlock(); block.setCreated(); getPropertyStore().encodeValue( block, index.getKeyId(), value ); PropertyRecord host = addPropertyBlockToPrimitive( block, relRecord, /*isNode*/false ); assert assertPropertyChain( relRecord ); return block.newPropertyData( host, value ); } @Override public PropertyData nodeAddProperty( long nodeId, PropertyIndex index, Object value ) { NodeRecord nodeRecord = getNodeRecord( nodeId ); if ( nodeRecord == null ) { nodeRecord = getNodeStore().getRecord( nodeId ); addNodeRecord( nodeRecord ); } if ( !nodeRecord.inUse() ) { throw new IllegalStateException( "Property add on node[" + nodeId + "] illegal since it has been deleted." ); } assert assertPropertyChain( nodeRecord ); PropertyBlock block = new PropertyBlock(); block.setCreated(); /* * Encoding has to be set here before anything is changed, * since an exception could be thrown in encodeValue now and tx not marked * rollback only. */ getPropertyStore().encodeValue( block, index.getKeyId(), value ); PropertyRecord host = addPropertyBlockToPrimitive( block, nodeRecord, /*isNode*/true ); assert assertPropertyChain( nodeRecord ); return block.newPropertyData( host, value ); } private PropertyRecord addPropertyBlockToPrimitive( PropertyBlock block, PrimitiveRecord primitive, boolean isNode ) { assert assertPropertyChain( primitive ); int newBlockSizeInBytes = block.getSize(); /* * Here we could either iterate over the whole chain or just go for the first record * which is the most likely to be the less full one. Currently we opt for the second * to perform better. */ PropertyRecord host = null; long firstProp = primitive.getNextProp(); if ( firstProp != Record.NO_NEXT_PROPERTY.intValue() ) { // We do not store in map - might not have enough space PropertyRecord propRecord = getPropertyRecord( firstProp, false, false ); assert propRecord.getPrevProp() == Record.NO_PREVIOUS_PROPERTY.intValue() : propRecord + " for " + primitive; assert propRecord.inUse() : propRecord; int propSize = propRecord.size(); assert propSize > 0 : propRecord; if ( propSize + newBlockSizeInBytes <= PropertyType.getPayloadSize() ) { host = propRecord; host.addPropertyBlock( block ); host.setChanged(); } } if ( host == null ) { // First record in chain didn't fit, make new one host = new PropertyRecord( getPropertyStore().nextId() ); host.setCreated(); if ( primitive.getNextProp() != Record.NO_NEXT_PROPERTY.intValue() ) { PropertyRecord prevProp = getPropertyRecord( primitive.getNextProp(), true, true ); if ( isNode ) { addNodeRecord( (NodeRecord) primitive ); } else { addRelationshipRecord( (RelationshipRecord) primitive ); } assert prevProp.getPrevProp() == Record.NO_PREVIOUS_PROPERTY.intValue(); prevProp.setPrevProp( host.getId() ); host.setNextProp( prevProp.getId() ); prevProp.setChanged(); } primitive.setNextProp( host.getId() ); host.addPropertyBlock( block ); host.setInUse( true ); } // Ok, here host does for the job. Use it if ( isNode ) { host.setNodeId( primitive.getId() ); } else { host.setRelId( primitive.getId() ); } addPropertyRecord( host ); assert assertPropertyChain( primitive ); return host; } @Override public void relationshipCreate( long id, int type, long firstNodeId, long secondNodeId ) { NodeRecord firstNode = getNodeRecord( firstNodeId ); if ( firstNode == null ) { firstNode = getNodeStore().getRecord( firstNodeId ); addNodeRecord( firstNode ); } if ( !firstNode.inUse() ) { throw new IllegalStateException( "First node[" + firstNodeId + "] is deleted and cannot be used to create a relationship" ); } NodeRecord secondNode = getNodeRecord( secondNodeId ); if ( secondNode == null ) { secondNode = getNodeStore().getRecord( secondNodeId ); addNodeRecord( secondNode ); } if ( !secondNode.inUse() ) { throw new IllegalStateException( "Second node[" + secondNodeId + "] is deleted and cannot be used to create a relationship" ); } RelationshipRecord record = new RelationshipRecord( id, firstNodeId, secondNodeId, type ); record.setInUse( true ); record.setCreated(); addRelationshipRecord( record ); connectRelationship( firstNode, secondNode, record ); } private void connectRelationship( NodeRecord firstNode, NodeRecord secondNode, RelationshipRecord rel ) { assert firstNode.getNextRel() != rel.getId(); assert secondNode.getNextRel() != rel.getId(); rel.setFirstNextRel( firstNode.getNextRel() ); rel.setSecondNextRel( secondNode.getNextRel() ); connect( firstNode, rel ); connect( secondNode, rel ); firstNode.setNextRel( rel.getId() ); secondNode.setNextRel( rel.getId() ); } private void connect( NodeRecord node, RelationshipRecord rel ) { if ( node.getNextRel() != Record.NO_NEXT_RELATIONSHIP.intValue() ) { Relationship lockableRel = new LockableRelationship( node.getNextRel() ); getWriteLock( lockableRel ); RelationshipRecord nextRel = getRelationshipRecord( node.getNextRel() ); if ( nextRel == null ) { nextRel = getRelationshipStore().getRecord( node.getNextRel() ); addRelationshipRecord( nextRel ); } boolean changed = false; if ( nextRel.getFirstNode() == node.getId() ) { nextRel.setFirstPrevRel( rel.getId() ); changed = true; } if ( nextRel.getSecondNode() == node.getId() ) { nextRel.setSecondPrevRel( rel.getId() ); changed = true; } if ( !changed ) { throw new InvalidRecordException( node + " dont match " + nextRel ); } } } @Override public void nodeCreate( long nodeId ) { NodeRecord nodeRecord = new NodeRecord( nodeId ); nodeRecord.setInUse( true ); nodeRecord.setCreated(); addNodeRecord( nodeRecord ); } @Override public String loadIndex( int id ) { PropertyIndexStore indexStore = getPropertyStore().getIndexStore(); PropertyIndexRecord index = getPropertyIndexRecord( id ); if ( index == null ) { index = indexStore.getRecord( id ); } if ( index.isLight() ) { indexStore.makeHeavy( index ); } return indexStore.getStringFor( index ); } @Override public PropertyIndexData[] loadPropertyIndexes( int count ) { PropertyIndexStore indexStore = getPropertyStore().getIndexStore(); return indexStore.getPropertyIndexes( count ); } @Override public void createPropertyIndex( String key, int id ) { PropertyIndexRecord record = new PropertyIndexRecord( id ); record.setInUse( true ); record.setCreated(); PropertyIndexStore propIndexStore = getPropertyStore().getIndexStore(); int keyBlockId = propIndexStore.nextKeyBlockId(); record.setKeyBlockId( keyBlockId ); Collection<DynamicRecord> keyRecords = propIndexStore.allocateKeyRecords( keyBlockId, encodeString( key ) ); for ( DynamicRecord keyRecord : keyRecords ) { record.addKeyRecord( keyRecord ); } addPropertyIndexRecord( record ); } @Override public void createRelationshipType( int id, String name ) { RelationshipTypeRecord record = new RelationshipTypeRecord( id ); record.setInUse( true ); record.setCreated(); int blockId = (int) getRelationshipTypeStore().nextBlockId(); record.setTypeBlock( blockId ); // int length = name.length(); // char[] chars = new char[length]; // name.getChars( 0, length, chars, 0 ); Collection<DynamicRecord> typeNameRecords = getRelationshipTypeStore().allocateTypeNameRecords( blockId, encodeString( name ) ); for ( DynamicRecord typeRecord : typeNameRecords ) { record.addTypeRecord( typeRecord ); } addRelationshipTypeRecord( record ); } static class CommandSorter implements Comparator<Command>, Serializable { public int compare( Command o1, Command o2 ) { long id1 = o1.getKey(); long id2 = o2.getKey(); long diff = id1 - id2; if ( diff > Integer.MAX_VALUE ) { return Integer.MAX_VALUE; } else if ( diff < Integer.MIN_VALUE ) { return Integer.MIN_VALUE; } else { return (int) diff; } } @Override public boolean equals( Object o ) { if ( o instanceof CommandSorter ) { return true; } return false; } @Override public int hashCode() { return 3217; } } void addNodeRecord( NodeRecord record ) { nodeRecords.put( record.getId(), record ); } NodeRecord getNodeRecord( long nodeId ) { return nodeRecords.get( nodeId ); } void addRelationshipRecord( RelationshipRecord record ) { relRecords.put( record.getId(), record ); } RelationshipRecord getRelationshipRecord( long relId ) { return relRecords.get( relId ); } void addPropertyRecord( PropertyRecord record ) { propertyRecords.put( record.getId(), record ); } PropertyRecord getPropertyRecord( long propertyId, boolean light, boolean store ) { PropertyRecord result = propertyRecords.get( propertyId ); if ( result == null ) { if ( light ) { result = getPropertyStore().getLightRecord( propertyId ); } else { result = getPropertyStore().getRecord( propertyId ); } if ( store ) { addPropertyRecord( result ); } } return result; } void addRelationshipTypeRecord( RelationshipTypeRecord record ) { relTypeRecords.put( record.getId(), record ); } void addPropertyIndexRecord( PropertyIndexRecord record ) { propIndexRecords.put( record.getId(), record ); } PropertyIndexRecord getPropertyIndexRecord( int id ) { return propIndexRecords.get( id ); } private static class LockableRelationship implements Relationship { private final long id; LockableRelationship( long id ) { this.id = id; } @Override public void delete() { throw new UnsupportedOperationException( "Lockable rel" ); } @Override public Node getEndNode() { throw new UnsupportedOperationException( "Lockable rel" ); } @Override public long getId() { return this.id; } @Override public GraphDatabaseService getGraphDatabase() { throw new UnsupportedOperationException( "Lockable rel" ); } @Override public Node[] getNodes() { throw new UnsupportedOperationException( "Lockable rel" ); } @Override public Node getOtherNode( Node node ) { throw new UnsupportedOperationException( "Lockable rel" ); } @Override public Object getProperty( String key ) { throw new UnsupportedOperationException( "Lockable rel" ); } @Override public Object getProperty( String key, Object defaultValue ) { throw new UnsupportedOperationException( "Lockable rel" ); } @Override public Iterable<String> getPropertyKeys() { throw new UnsupportedOperationException( "Lockable rel" ); } @Override public Iterable<Object> getPropertyValues() { throw new UnsupportedOperationException( "Lockable rel" ); } @Override public Node getStartNode() { throw new UnsupportedOperationException( "Lockable rel" ); } @Override public RelationshipType getType() { throw new UnsupportedOperationException( "Lockable rel" ); } @Override public boolean isType( RelationshipType type ) { throw new UnsupportedOperationException( "Lockable rel" ); } @Override public boolean hasProperty( String key ) { throw new UnsupportedOperationException( "Lockable rel" ); } @Override public Object removeProperty( String key ) { throw new UnsupportedOperationException( "Lockable rel" ); } @Override public void setProperty( String key, Object value ) { throw new UnsupportedOperationException( "Lockable rel" ); } @Override public boolean equals( Object o ) { if ( !(o instanceof Relationship) ) { return false; } return this.getId() == ((Relationship) o).getId(); } @Override public int hashCode() { return (int) (( id >>> 32 ) ^ id ); } @Override public String toString() { return "Lockable relationship #" + this.getId(); } } @Override public RelIdArray getCreatedNodes() { RelIdArray createdNodes = new RelIdArray( null ); for ( NodeRecord record : nodeRecords.values() ) { if ( record.isCreated() ) { // TODO Direction doesn't matter... misuse of RelIdArray? createdNodes.add( record.getId(), DirectionWrapper.OUTGOING ); } } return createdNodes; } @Override public boolean isNodeCreated( long nodeId ) { NodeRecord record = nodeRecords.get( nodeId ); if ( record != null ) { return record.isCreated(); } return false; } @Override public boolean isRelationshipCreated( long relId ) { RelationshipRecord record = relRecords.get( relId ); if ( record != null ) { return record.isCreated(); } return false; } @Override public int getKeyIdForProperty( PropertyData property ) { return ReadTransaction.getKeyIdForProperty( property, getPropertyStore() ); } @Override public XAResource getXAResource() { return xaConnection.getXaResource(); } @Override public void destroy() { xaConnection.destroy(); } @Override public void setXaConnection( XaConnection connection ) { this.xaConnection = connection; } @Override public RelationshipTypeData[] loadRelationshipTypes() { RelationshipTypeData relTypeData[] = neoStore.getRelationshipTypeStore().getRelationshipTypes();; RelationshipTypeData rawRelTypeData[] = new RelationshipTypeData[relTypeData.length]; for ( int i = 0; i < relTypeData.length; i++ ) { rawRelTypeData[i] = new RelationshipTypeData( relTypeData[i].getId(), relTypeData[i].getName() ); } return rawRelTypeData; } private boolean assertPropertyChain( PrimitiveRecord primitive ) { List<PropertyRecord> toCheck = new LinkedList<PropertyRecord>(); long nextIdToFetch = primitive.getNextProp(); while ( nextIdToFetch != Record.NO_NEXT_PROPERTY.intValue() ) { PropertyRecord toAdd = getPropertyRecord( nextIdToFetch, true, false ); toCheck.add( toAdd ); assert toAdd.inUse() : primitive + "->" + Arrays.toString( toCheck.toArray() ); nextIdToFetch = toAdd.getNextProp(); } if ( toCheck.isEmpty() ) { assert primitive.getNextProp() == Record.NO_NEXT_PROPERTY.intValue() : primitive; return true; } PropertyRecord first = toCheck.get( 0 ); PropertyRecord last = toCheck.get( toCheck.size() - 1 ); assert first.getPrevProp() == Record.NO_PREVIOUS_PROPERTY.intValue() : primitive + "->" + Arrays.toString( toCheck.toArray() ); assert last.getNextProp() == Record.NO_NEXT_PROPERTY.intValue() : primitive + "->" + Arrays.toString( toCheck.toArray() ); PropertyRecord current, previous = first; for ( int i = 1; i < toCheck.size(); i++ ) { current = toCheck.get( i ); assert current.getPrevProp() == previous.getId() : primitive + "->" + Arrays.toString( toCheck.toArray() ); assert previous.getNextProp() == current.getId() : primitive + "->" + Arrays.toString( toCheck.toArray() ); previous = current; } return true; } }