/*
 * 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();
    }
}