/* * Copyright (c) 2002-2009 "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 Affero 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 Affero General Public License for more details. * * You should have received a copy of the GNU Affero General Public License * along with this program. If not, see <http://www.gnu.org/licenses/>. */ package org.neo4j.kernel.impl.nioneo.xa; import java.io.Serializable; import java.util.ArrayList; import java.util.Collection; import java.util.Comparator; import java.util.HashMap; import java.util.List; import java.util.Map; 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.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.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.RelationshipChainPosition; import org.neo4j.kernel.impl.nioneo.store.RelationshipData; 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.transaction.LockManager; import org.neo4j.kernel.impl.transaction.LockType; import org.neo4j.kernel.impl.transaction.xaframework.XaCommand; 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.IntArray; /** * Transaction containing {@link Command commands} reflecting the operations * performed in the transaction. */ class WriteTransaction extends XaTransaction { private final Map<Integer,NodeRecord> nodeRecords = new HashMap<Integer,NodeRecord>(); private final Map<Integer,PropertyRecord> propertyRecords = new HashMap<Integer,PropertyRecord>(); private final Map<Integer,RelationshipRecord> relRecords = new HashMap<Integer,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; WriteTransaction( int identifier, XaLogicalLog log, NeoStore neoStore, LockReleaser lockReleaser, LockManager lockManager ) { super( identifier, log ); this.neoStore = neoStore; this.lockReleaser = lockReleaser; this.lockManager = lockManager; } 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; } 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 ); } } 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( 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( 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 ( DynamicRecord dynamicRecord : record .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(); if ( !isRecovered() ) { lockReleaser.rollback(); } } } private void removeRelationshipTypeFromCache( int id ) { lockReleaser.removeRelationshipTypeFromCache( id ); } private void removeRelationshipFromCache( int id ) { lockReleaser.removeRelationshipFromCache( id ); } private void removeNodeFromCache( int 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 ); } public void doCommit() throws XAException { if ( !isRecovered() && !prepared ) { throw new XAException( "Cannot commit non prepared transaction[" + getIdentifier() + "]" ); } if ( isRecovered() ) { commitRecovered(); return; } try { committed = true; CommandSorter sorter = new CommandSorter(); // reltypes java.util.Collections.sort( relTypeCommands, sorter ); for ( Command.RelationshipTypeCommand command : relTypeCommands ) { command.execute(); } // nodes java.util.Collections.sort( nodeCommands, sorter ); for ( Command.NodeCommand command : nodeCommands ) { command.execute(); } // relationships java.util.Collections.sort( relCommands, sorter ); for ( Command.RelationshipCommand command : relCommands ) { command.execute(); } java.util.Collections.sort( propIndexCommands, sorter ); for ( Command.PropertyIndexCommand command : propIndexCommands ) { command.execute(); } // properties java.util.Collections.sort( propCommands, sorter ); for ( Command.PropertyCommand command : propCommands ) { command.execute(); } if ( !isRecovered() ) { lockReleaser.commit(); } } finally { nodeRecords.clear(); propertyRecords.clear(); relRecords.clear(); relTypeRecords.clear(); propIndexRecords.clear(); nodeCommands.clear(); propCommands.clear(); propIndexCommands.clear(); relCommands.clear(); relTypeCommands.clear(); } } private void commitRecovered() { try { committed = true; CommandSorter sorter = new CommandSorter(); // reltypes java.util.Collections.sort( relTypeCommands, sorter ); for ( Command.RelationshipTypeCommand command : relTypeCommands ) { command.execute(); addRelationshipType( command.getKey() ); } // nodes java.util.Collections.sort( nodeCommands, sorter ); for ( Command.NodeCommand command : nodeCommands ) { command.execute(); removeNodeFromCache( command.getKey() ); } // relationships java.util.Collections.sort( relCommands, sorter ); for ( Command.RelationshipCommand command : relCommands ) { command.execute(); removeRelationshipFromCache( command.getKey() ); } java.util.Collections.sort( propIndexCommands, sorter ); for ( Command.PropertyIndexCommand command : propIndexCommands ) { command.execute(); addPropertyIndexCommand( command.getKey() ); } // properties java.util.Collections.sort( propCommands, sorter ); for ( Command.PropertyCommand command : propCommands ) { command.execute(); removePropertyFromCache( command ); } neoStore.updateIdGenerators(); if ( !isRecovered() ) { lockReleaser.commit(); } } 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 ) { lockReleaser.clearCache(); } 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(); } public boolean nodeLoadLight( int nodeId ) { NodeRecord nodeRecord = getNodeRecord( nodeId ); if ( nodeRecord != null ) { return true; } return getNodeStore().loadLightNode( nodeId ); } public RelationshipData relationshipLoad( int id ) { RelationshipRecord relRecord = getRelationshipRecord( id ); if ( relRecord != null ) { // if ( !relRecord.inUse() ) // { // return null; // } return new RelationshipData( id, relRecord.getFirstNode(), relRecord.getSecondNode(), relRecord.getType() ); } relRecord = getRelationshipStore().getLightRel( id ); if ( relRecord != null ) { return new RelationshipData( id, relRecord.getFirstNode(), relRecord.getSecondNode(), relRecord.getType() ); } return null; } ArrayMap<Integer,PropertyData> nodeDelete( int 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<Integer,PropertyData> propertyMap = new ArrayMap<Integer,PropertyData>( 9, false, true ); int nextProp = nodeRecord.getNextProp(); while ( nextProp != Record.NO_NEXT_PROPERTY.intValue() ) { PropertyRecord propRecord = getPropertyRecord( nextProp ); if ( propRecord == null ) { propRecord = getPropertyStore().getRecord( nextProp ); addPropertyRecord( propRecord ); } if ( propRecord.isLight() ) { getPropertyStore().makeHeavy( propRecord ); } if ( !propRecord.isCreated() ) { if ( !propRecord.isChanged() ) { propertyMap.put( propRecord.getKeyIndexId(), new PropertyData( propRecord.getId(), propertyGetValueOrNull( propRecord ) ) ); } else { // we have to re-read committed value since property has // changed and old value is erased in memory PropertyRecord diskValue = getPropertyStore().getRecord( propRecord.getId() ); getPropertyStore().makeHeavy( diskValue ); propertyMap.put( diskValue.getKeyIndexId(), new PropertyData( diskValue.getId(), propertyGetValueOrNull( diskValue ) ) ); } } nextProp = propRecord.getNextProp(); propRecord.setInUse( false ); // TODO: update count on property index record for ( DynamicRecord valueRecord : propRecord.getValueRecords() ) { valueRecord.setInUse( false ); } } return propertyMap; } ArrayMap<Integer,PropertyData> relDelete( int 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<Integer,PropertyData> propertyMap = new ArrayMap<Integer,PropertyData>( 9, false, true ); int nextProp = record.getNextProp(); while ( nextProp != Record.NO_NEXT_PROPERTY.intValue() ) { PropertyRecord propRecord = getPropertyRecord( nextProp ); if ( propRecord == null ) { propRecord = getPropertyStore().getRecord( nextProp ); addPropertyRecord( propRecord ); } if ( propRecord.isLight() ) { getPropertyStore().makeHeavy( propRecord ); } if ( !propRecord.isCreated() ) { if ( !propRecord.isChanged() ) { propertyMap.put( propRecord.getKeyIndexId(), new PropertyData( propRecord.getId(), propertyGetValueOrNull( propRecord ) ) ); } else { // we have to re-read committed value since property has // changed and old value is erased in memory PropertyRecord diskValue = getPropertyStore().getRecord( propRecord.getId() ); getPropertyStore().makeHeavy( diskValue ); propertyMap.put( diskValue.getKeyIndexId(), new PropertyData( diskValue.getId(), propertyGetValueOrNull( diskValue ) ) ); } } nextProp = propRecord.getNextProp(); propRecord.setInUse( false ); // TODO: update count on property index record for ( DynamicRecord valueRecord : propRecord.getValueRecords() ) { valueRecord.setInUse( false ); } } disconnectRelationship( record ); updateNodes( record ); record.setInUse( false ); return propertyMap; } 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 ); } if ( prevRel.getFirstNode() == rel.getFirstNode() ) { prevRel.setFirstNextRel( rel.getFirstNextRel() ); } else if ( prevRel.getSecondNode() == rel.getFirstNode() ) { prevRel.setSecondNextRel( rel.getFirstNextRel() ); } else { 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 ); } if ( nextRel.getFirstNode() == rel.getFirstNode() ) { nextRel.setFirstPrevRel( rel.getFirstPrevRel() ); } else if ( nextRel.getSecondNode() == rel.getFirstNode() ) { nextRel.setSecondPrevRel( rel.getFirstPrevRel() ); } else { 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 ); } if ( prevRel.getFirstNode() == rel.getSecondNode() ) { prevRel.setFirstNextRel( rel.getSecondNextRel() ); } else if ( prevRel.getSecondNode() == rel.getSecondNode() ) { prevRel.setSecondNextRel( rel.getSecondNextRel() ); } else { 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 ); } if ( nextRel.getFirstNode() == rel.getSecondNode() ) { nextRel.setFirstPrevRel( rel.getSecondPrevRel() ); } else if ( nextRel.getSecondNode() == rel.getSecondNode() ) { nextRel.setSecondPrevRel( rel.getSecondPrevRel() ); } else { throw new InvalidRecordException( nextRel + " don't match " + rel ); } } } private void getWriteLock( Relationship lockableRel ) { lockManager.getWriteLock( lockableRel ); lockReleaser.addLockToTransaction( lockableRel, LockType.WRITE ); } public RelationshipChainPosition getRelationshipChainPosition( int nodeId ) { NodeRecord nodeRecord = getNodeRecord( nodeId ); if ( nodeRecord == null ) { nodeRecord = getNodeStore().getRecord( nodeId ); } // else if ( !nodeRecord.inUse() ) // { // return new RelationshipChainPosition( // Record.NO_NEXT_RELATIONSHIP.intValue() ); // } int nextRel = nodeRecord.getNextRel(); return new RelationshipChainPosition( nextRel ); } public Iterable<RelationshipData> getMoreRelationships( int nodeId, RelationshipChainPosition position ) { int nextRel = position.getNextRecord(); List<RelationshipData> rels = new ArrayList<RelationshipData>(); for ( int i = 0; i < getRelGrabSize() && nextRel != Record.NO_NEXT_RELATIONSHIP.intValue(); i++ ) { RelationshipRecord relRecord = getRelationshipRecord( nextRel ); if ( relRecord == null ) { relRecord = getRelationshipStore().getChainRecord( nextRel ); } if ( relRecord == null ) { // return what we got so far position.setNextRecord( Record.NO_NEXT_RELATIONSHIP.intValue() ); return rels; } int firstNode = relRecord.getFirstNode(); int secondNode = relRecord.getSecondNode(); if ( relRecord.inUse() && !relRecord.isCreated() ) { rels.add( new RelationshipData( relRecord.getId(), firstNode, secondNode, relRecord.getType() ) ); } else { i--; } if ( firstNode == nodeId ) { nextRel = relRecord.getFirstNextRel(); } else if ( secondNode == nodeId ) { nextRel = relRecord.getSecondNextRel(); } else { throw new InvalidRecordException( "Node[" + nodeId + "] not part of firstNode[" + firstNode + "] or secondNode[" + secondNode + "]" ); } } position.setNextRecord( nextRel ); return rels; } 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() ); } } void relRemoveProperty( int relId, int propertyId ) { 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." ); } PropertyRecord propRecord = getPropertyRecord( propertyId ); if ( propRecord == null ) { propRecord = getPropertyStore().getRecord( propertyId ); addPropertyRecord( propRecord ); } if ( !propRecord.inUse() ) { throw new IllegalStateException( "Unable to delete property[" + propertyId + "] since it is already deleted." ); } propRecord.setRelId( relId ); if ( propRecord.isLight() ) { getPropertyStore().makeHeavy( propRecord ); } propRecord.setInUse( false ); // TODO: update count on property index record for ( DynamicRecord valueRecord : propRecord.getValueRecords() ) { if ( valueRecord.inUse() ) { valueRecord.setInUse( false, propRecord.getType().intValue() ); } } int prevProp = propRecord.getPrevProp(); int nextProp = propRecord.getNextProp(); if ( relRecord.getNextProp() == propertyId ) { relRecord.setNextProp( nextProp ); // re-adding not a problem addRelationshipRecord( relRecord ); } if ( prevProp != Record.NO_PREVIOUS_PROPERTY.intValue() ) { PropertyRecord prevPropRecord = getPropertyRecord( prevProp ); if ( prevPropRecord == null ) { prevPropRecord = getPropertyStore().getLightRecord( prevProp ); addPropertyRecord( prevPropRecord ); } assert prevPropRecord.inUse(); prevPropRecord.setNextProp( nextProp ); } if ( nextProp != Record.NO_NEXT_PROPERTY.intValue() ) { PropertyRecord nextPropRecord = getPropertyRecord( nextProp ); if ( nextPropRecord == null ) { nextPropRecord = getPropertyStore().getLightRecord( nextProp ); addPropertyRecord( nextPropRecord ); } assert nextPropRecord.inUse(); nextPropRecord.setPrevProp( prevProp ); } } public ArrayMap<Integer,PropertyData> relGetProperties( int relId, boolean light ) { RelationshipRecord relRecord = getRelationshipRecord( relId ); if ( relRecord != null ) { if ( !relRecord.inUse() && !light ) { throw new IllegalStateException( "Relationship[" + relId + "] has been deleted in this tx" ); } } else { relRecord = getRelationshipStore().getRecord( relId ); } if ( !relRecord.inUse() ) { throw new InvalidRecordException( "Relationship[" + relId + "] not in use" ); } int nextProp = relRecord.getNextProp(); ArrayMap<Integer,PropertyData> propertyMap = new ArrayMap<Integer,PropertyData>( 9, false, true ); while ( nextProp != Record.NO_NEXT_PROPERTY.intValue() ) { PropertyRecord propRecord = getPropertyRecord( nextProp ); if ( propRecord == null ) { propRecord = getPropertyStore().getLightRecord( nextProp ); } if ( !propRecord.isCreated() ) { propertyMap.put( propRecord.getKeyIndexId(), new PropertyData( propRecord.getId(), propertyGetValueOrNull( propRecord ) ) ); } nextProp = propRecord.getNextProp(); } return propertyMap; } ArrayMap<Integer,PropertyData> nodeGetProperties( int nodeId, boolean light ) { NodeRecord nodeRecord = getNodeRecord( nodeId ); if ( nodeRecord != null ) { if ( !nodeRecord.inUse() && !light ) { throw new IllegalStateException( "Node[" + nodeId + "] has been deleted in this tx" ); } } else { nodeRecord = getNodeStore().getRecord( nodeId ); } if ( !nodeRecord.inUse() ) { throw new InvalidRecordException( "Node[" + nodeId + "] not in use" ); } int nextProp = nodeRecord.getNextProp(); ArrayMap<Integer,PropertyData> propertyMap = new ArrayMap<Integer,PropertyData>( 9, false, true ); while ( nextProp != Record.NO_NEXT_PROPERTY.intValue() ) { PropertyRecord propRecord = getPropertyRecord( nextProp ); if ( propRecord == null ) { propRecord = getPropertyStore().getLightRecord( nextProp ); } if ( !propRecord.isCreated() ) { propertyMap.put( propRecord.getKeyIndexId(), new PropertyData( propRecord.getId(), propertyGetValueOrNull( propRecord ) ) ); } nextProp = propRecord.getNextProp(); } return propertyMap; } public Object propertyGetValueOrNull( PropertyRecord propertyRecord ) { PropertyType type = propertyRecord.getType(); if ( type == PropertyType.INT ) { return (int) propertyRecord.getPropBlock(); } if ( type == PropertyType.STRING ) { if ( propertyRecord.isLight() ) { return null; } return getPropertyStore().getStringFor( propertyRecord ); } if ( type == PropertyType.BOOL ) { if ( propertyRecord.getPropBlock() == 1 ) { return Boolean.valueOf( true ); } return Boolean.valueOf( false ); } if ( type == PropertyType.DOUBLE ) { return new Double( Double.longBitsToDouble( propertyRecord.getPropBlock() ) ); } if ( type == PropertyType.FLOAT ) { return new Float( Float.intBitsToFloat( (int) propertyRecord.getPropBlock() ) ); } if ( type == PropertyType.LONG ) { return propertyRecord.getPropBlock(); } if ( type == PropertyType.BYTE ) { return (byte) propertyRecord.getPropBlock(); } if ( type == PropertyType.CHAR ) { return (char) propertyRecord.getPropBlock(); } if ( type == PropertyType.ARRAY ) { if ( propertyRecord.isLight() ) { return null; } return getPropertyStore().getArrayFor( propertyRecord ); } if ( type == PropertyType.SHORT ) { return (short) propertyRecord.getPropBlock(); } throw new InvalidRecordException( "Unknown type: " + type + " on " + propertyRecord ); } public Object propertyGetValue( int id ) { PropertyRecord propertyRecord = getPropertyRecord( id ); if ( propertyRecord == null ) { propertyRecord = getPropertyStore().getRecord( id ); } if ( propertyRecord.isLight() ) { getPropertyStore().makeHeavy( propertyRecord ); } PropertyType type = propertyRecord.getType(); if ( type == PropertyType.INT ) { return (int) propertyRecord.getPropBlock(); } if ( type == PropertyType.STRING ) { return getPropertyStore().getStringFor( propertyRecord ); } if ( type == PropertyType.BOOL ) { if ( propertyRecord.getPropBlock() == 1 ) { return Boolean.valueOf( true ); } return Boolean.valueOf( false ); } if ( type == PropertyType.DOUBLE ) { return new Double( Double.longBitsToDouble( propertyRecord.getPropBlock() ) ); } if ( type == PropertyType.FLOAT ) { return new Float( Float.intBitsToFloat( (int) propertyRecord.getPropBlock() ) ); } if ( type == PropertyType.LONG ) { return propertyRecord.getPropBlock(); } if ( type == PropertyType.BYTE ) { return (byte) propertyRecord.getPropBlock(); } if ( type == PropertyType.CHAR ) { return (char) propertyRecord.getPropBlock(); } if ( type == PropertyType.ARRAY ) { return getPropertyStore().getArrayFor( propertyRecord ); } if ( type == PropertyType.SHORT ) { return (short) propertyRecord.getPropBlock(); } throw new InvalidRecordException( "Unknown type: " + type + " on " + propertyRecord ); } void nodeRemoveProperty( int nodeId, int propertyId ) { NodeRecord nodeRecord = getNodeRecord( nodeId ); if ( nodeRecord == null ) { nodeRecord = getNodeStore().getRecord( nodeId ); } if ( !nodeRecord.inUse() ) { throw new IllegalStateException( "Property remove on node[" + nodeId + "] illegal since it has been deleted." ); } PropertyRecord propRecord = getPropertyRecord( propertyId ); if ( propRecord == null ) { propRecord = getPropertyStore().getRecord( propertyId ); addPropertyRecord( propRecord ); } if ( !propRecord.inUse() ) { throw new IllegalStateException( "Unable to delete property[" + propertyId + "] since it is already deleted." ); } propRecord.setNodeId( nodeId ); if ( propRecord.isLight() ) { getPropertyStore().makeHeavy( propRecord ); } propRecord.setInUse( false ); // TODO: update count on property index record for ( DynamicRecord valueRecord : propRecord.getValueRecords() ) { if ( valueRecord.inUse() ) { valueRecord.setInUse( false, propRecord.getType().intValue() ); } } int prevProp = propRecord.getPrevProp(); int nextProp = propRecord.getNextProp(); if ( nodeRecord.getNextProp() == propertyId ) { nodeRecord.setNextProp( nextProp ); // re-adding not a problem addNodeRecord( nodeRecord ); } if ( prevProp != Record.NO_PREVIOUS_PROPERTY.intValue() ) { PropertyRecord prevPropRecord = getPropertyRecord( prevProp ); if ( prevPropRecord == null ) { prevPropRecord = getPropertyStore().getLightRecord( prevProp ); addPropertyRecord( prevPropRecord ); } assert prevPropRecord.inUse(); prevPropRecord.setNextProp( nextProp ); } if ( nextProp != Record.NO_NEXT_PROPERTY.intValue() ) { PropertyRecord nextPropRecord = getPropertyRecord( nextProp ); if ( nextPropRecord == null ) { nextPropRecord = getPropertyStore().getLightRecord( nextProp ); addPropertyRecord( nextPropRecord ); } assert nextPropRecord.inUse(); nextPropRecord.setPrevProp( prevProp ); } } void relChangeProperty( int relId, int propertyId, 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." ); } PropertyRecord propertyRecord = getPropertyRecord( propertyId ); if ( propertyRecord == null ) { propertyRecord = getPropertyStore().getRecord( propertyId ); addPropertyRecord( propertyRecord ); } if ( !propertyRecord.inUse() ) { throw new IllegalStateException( "Unable to change property[" + propertyId + "] since it is deleted." ); } propertyRecord.setRelId( relId ); if ( propertyRecord.isLight() ) { getPropertyStore().makeHeavy( propertyRecord ); } propertyRecord.setChanged(); if ( propertyRecord.getType() == PropertyType.STRING ) { for ( DynamicRecord record : propertyRecord.getValueRecords() ) { if ( record.inUse() ) { record.setInUse( false, PropertyType.STRING.intValue() ); } } } else if ( propertyRecord.getType() == PropertyType.ARRAY ) { for ( DynamicRecord record : propertyRecord.getValueRecords() ) { if ( record.inUse() ) { record.setInUse( false, PropertyType.ARRAY.intValue() ); } } } getPropertyStore().encodeValue( propertyRecord, value ); addPropertyRecord( propertyRecord ); } void nodeChangeProperty( int nodeId, int propertyId, 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." ); } PropertyRecord propertyRecord = getPropertyRecord( propertyId ); if ( propertyRecord == null ) { propertyRecord = getPropertyStore().getRecord( propertyId ); addPropertyRecord( propertyRecord ); } if ( !propertyRecord.inUse() ) { throw new IllegalStateException( "Unable to change property[" + propertyId + "] since it is deleted." ); } propertyRecord.setNodeId( nodeId ); if ( propertyRecord.isLight() ) { getPropertyStore().makeHeavy( propertyRecord ); } propertyRecord.setChanged(); if ( propertyRecord.getType() == PropertyType.STRING ) { for ( DynamicRecord record : propertyRecord.getValueRecords() ) { if ( record.inUse() ) { record.setInUse( false, PropertyType.STRING.intValue() ); } } } else if ( propertyRecord.getType() == PropertyType.ARRAY ) { for ( DynamicRecord record : propertyRecord.getValueRecords() ) { if ( record.inUse() ) { record.setInUse( false, PropertyType.ARRAY.intValue() ); } } } getPropertyStore().encodeValue( propertyRecord, value ); addPropertyRecord( propertyRecord ); } void relAddProperty( int relId, int propertyId, 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." ); } PropertyRecord propertyRecord = new PropertyRecord( propertyId ); propertyRecord.setInUse( true ); propertyRecord.setCreated(); if ( relRecord.getNextProp() != Record.NO_NEXT_RELATIONSHIP.intValue() ) { PropertyRecord prevProp = getPropertyRecord( relRecord.getNextProp() ); if ( prevProp == null ) { prevProp = getPropertyStore().getLightRecord( relRecord.getNextProp() ); addPropertyRecord( prevProp ); } assert prevProp.getPrevProp() == Record.NO_PREVIOUS_PROPERTY.intValue(); prevProp.setPrevProp( propertyId ); propertyRecord.setNextProp( prevProp.getId() ); } int keyIndexId = index.getKeyId(); propertyRecord.setKeyIndexId( keyIndexId ); getPropertyStore().encodeValue( propertyRecord, value ); relRecord.setNextProp( propertyId ); addPropertyRecord( propertyRecord ); } void nodeAddProperty( int nodeId, int propertyId, 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." ); } PropertyRecord propertyRecord = new PropertyRecord( propertyId ); propertyRecord.setInUse( true ); propertyRecord.setCreated(); // encoding has to be set here before anything is change // (exception is thrown in encodeValue now and tx not marked // rollback only getPropertyStore().encodeValue( propertyRecord, value ); if ( nodeRecord.getNextProp() != Record.NO_NEXT_PROPERTY.intValue() ) { PropertyRecord prevProp = getPropertyRecord( nodeRecord.getNextProp() ); if ( prevProp == null ) { prevProp = getPropertyStore().getLightRecord( nodeRecord.getNextProp() ); addPropertyRecord( prevProp ); } assert prevProp.getPrevProp() == Record.NO_PREVIOUS_PROPERTY.intValue(); prevProp.setPrevProp( propertyId ); propertyRecord.setNextProp( prevProp.getId() ); } int keyIndexId = index.getKeyId(); propertyRecord.setKeyIndexId( keyIndexId ); nodeRecord.setNextProp( propertyId ); addPropertyRecord( propertyRecord ); } void relationshipCreate( int id, int firstNodeId, int secondNodeId, int type ) { 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() ); if ( firstNode.getNextRel() != Record.NO_NEXT_RELATIONSHIP.intValue() ) { Relationship lockableRel = new LockableRelationship( firstNode.getNextRel() ); getWriteLock( lockableRel ); RelationshipRecord nextRel = getRelationshipRecord( firstNode.getNextRel() ); if ( nextRel == null ) { nextRel = getRelationshipStore().getRecord( firstNode.getNextRel() ); addRelationshipRecord( nextRel ); } if ( nextRel.getFirstNode() == firstNode.getId() ) { nextRel.setFirstPrevRel( rel.getId() ); } else if ( nextRel.getSecondNode() == firstNode.getId() ) { nextRel.setSecondPrevRel( rel.getId() ); } else { throw new InvalidRecordException( firstNode + " dont match " + nextRel ); } } if ( secondNode.getNextRel() != Record.NO_NEXT_RELATIONSHIP.intValue() ) { Relationship lockableRel = new LockableRelationship( secondNode.getNextRel() ); getWriteLock( lockableRel ); RelationshipRecord nextRel = getRelationshipRecord( secondNode.getNextRel() ); if ( nextRel == null ) { nextRel = getRelationshipStore().getRecord( secondNode.getNextRel() ); addRelationshipRecord( nextRel ); } if ( nextRel.getFirstNode() == secondNode.getId() ) { nextRel.setFirstPrevRel( rel.getId() ); } else if ( nextRel.getSecondNode() == secondNode.getId() ) { nextRel.setSecondPrevRel( rel.getId() ); } else { throw new InvalidRecordException( secondNode + " dont match " + nextRel ); } } firstNode.setNextRel( rel.getId() ); secondNode.setNextRel( rel.getId() ); } void nodeCreate( int nodeId ) { NodeRecord nodeRecord = new NodeRecord( nodeId ); nodeRecord.setInUse( true ); nodeRecord.setCreated(); addNodeRecord( nodeRecord ); } String getPropertyIndex( 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 ); } PropertyIndexData[] getPropertyIndexes( int count ) { PropertyIndexStore indexStore = getPropertyStore().getIndexStore(); return indexStore.getPropertyIndexes( count ); } void createPropertyIndex( int id, String key ) { PropertyIndexRecord record = new PropertyIndexRecord( id ); record.setInUse( true ); record.setCreated(); PropertyIndexStore propIndexStore = getPropertyStore().getIndexStore(); int keyBlockId = propIndexStore.nextKeyBlockId(); record.setKeyBlockId( keyBlockId ); int length = key.length(); char[] chars = new char[length]; key.getChars( 0, length, chars, 0 ); Collection<DynamicRecord> keyRecords = propIndexStore.allocateKeyRecords( keyBlockId, chars ); for ( DynamicRecord keyRecord : keyRecords ) { record.addKeyRecord( keyRecord ); } addPropertyIndexRecord( record ); } void relationshipTypeAdd( int id, String name ) { RelationshipTypeRecord record = new RelationshipTypeRecord( id ); record.setInUse( true ); record.setCreated(); int blockId = 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, chars ); for ( DynamicRecord typeRecord : typeNameRecords ) { record.addTypeRecord( typeRecord ); } addRelationshipTypeRecord( record ); } static class CommandSorter implements Comparator<Command>, Serializable { public int compare( Command o1, Command o2 ) { int id1 = o1.getKey(); int id2 = o2.getKey(); return id1 - id2; } public boolean equals( Object o ) { if ( o instanceof CommandSorter ) { return true; } return false; } public int hashCode() { return 3217; } } void addNodeRecord( NodeRecord record ) { nodeRecords.put( record.getId(), record ); } NodeRecord getNodeRecord( int nodeId ) { return nodeRecords.get( nodeId ); } void addRelationshipRecord( RelationshipRecord record ) { relRecords.put( record.getId(), record ); } RelationshipRecord getRelationshipRecord( int relId ) { return relRecords.get( relId ); } void addPropertyRecord( PropertyRecord record ) { propertyRecords.put( record.getId(), record ); } PropertyRecord getPropertyRecord( int propertyId ) { return propertyRecords.get( propertyId ); } 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 int id; LockableRelationship( int id ) { this.id = id; } public void delete() { throw new UnsupportedOperationException( "Lockable rel" ); } public Node getEndNode() { throw new UnsupportedOperationException( "Lockable rel" ); } public long getId() { return this.id; } public GraphDatabaseService getGraphDatabase() { throw new UnsupportedOperationException( "Lockable rel" ); } public Node[] getNodes() { throw new UnsupportedOperationException( "Lockable rel" ); } public Node getOtherNode( Node node ) { throw new UnsupportedOperationException( "Lockable rel" ); } public Object getProperty( String key ) { throw new UnsupportedOperationException( "Lockable rel" ); } public Object getProperty( String key, Object defaultValue ) { throw new UnsupportedOperationException( "Lockable rel" ); } public Iterable<String> getPropertyKeys() { throw new UnsupportedOperationException( "Lockable rel" ); } public Iterable<Object> getPropertyValues() { throw new UnsupportedOperationException( "Lockable rel" ); } public Node getStartNode() { throw new UnsupportedOperationException( "Lockable rel" ); } public RelationshipType getType() { throw new UnsupportedOperationException( "Lockable rel" ); } public boolean isType( RelationshipType type ) { throw new UnsupportedOperationException( "Lockable rel" ); } public boolean hasProperty( String key ) { throw new UnsupportedOperationException( "Lockable rel" ); } public Object removeProperty( String key ) { throw new UnsupportedOperationException( "Lockable rel" ); } public void setProperty( String key, Object value ) { throw new UnsupportedOperationException( "Lockable rel" ); } public boolean equals( Object o ) { if ( !(o instanceof Relationship) ) { return false; } return this.getId() == ((Relationship) o).getId(); } public int hashCode() { return id; } public String toString() { return "Lockable relationship #" + this.getId(); } } public IntArray getCreatedNodes() { IntArray createdNodes = new IntArray(); for ( NodeRecord record : nodeRecords.values() ) { if ( record.isCreated() ) { createdNodes.add( record.getId() ); } } return createdNodes; } public boolean nodeCreated( int nodeId ) { NodeRecord record = nodeRecords.get( nodeId ); if ( record != null ) { return record.isCreated(); } return false; } public boolean relCreated( int relId ) { RelationshipRecord record = relRecords.get( relId ); if ( record != null ) { return record.isCreated(); } return false; } public int getKeyIdForProperty( int propertyId ) { PropertyRecord propRecord = getPropertyRecord( propertyId ); if ( propRecord == null ) { propRecord = getPropertyStore().getLightRecord( propertyId ); } return propRecord.getKeyIndexId(); } }