All Downloads are FREE. Search and download functionalities are using the official Maven repository.

org.neo4j.kernel.impl.nioneo.xa.Command Maven / Gradle / Ivy

Go to download

Neo4j kernel is a lightweight, embedded Java database designed to store data structured as graphs rather than tables. For more information, see http://neo4j.org.

There is a newer version: 5.26.0
Show newest version
/**
 * Copyright (c) 2002-2013 "Neo Technology,"
 * Network Engine for Objects in Lund AB [http://neotechnology.com]
 *
 * This file is part of Neo4j.
 *
 * Neo4j is free software: you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation, either version 3 of the License, or
 * (at your option) any later version.
 *
 * This program is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with this program.  If not, see .
 */
package org.neo4j.kernel.impl.nioneo.xa;

import java.io.IOException;
import java.nio.ByteBuffer;
import java.nio.channels.ReadableByteChannel;
import java.util.Collection;

import org.neo4j.kernel.impl.core.TransactionState;
import org.neo4j.kernel.impl.nioneo.store.DynamicRecord;
import org.neo4j.kernel.impl.nioneo.store.NeoStore;
import org.neo4j.kernel.impl.nioneo.store.NeoStoreRecord;
import org.neo4j.kernel.impl.nioneo.store.NodeRecord;
import org.neo4j.kernel.impl.nioneo.store.NodeStore;
import org.neo4j.kernel.impl.nioneo.store.PropertyBlock;
import org.neo4j.kernel.impl.nioneo.store.PropertyIndexRecord;
import org.neo4j.kernel.impl.nioneo.store.PropertyIndexStore;
import org.neo4j.kernel.impl.nioneo.store.PropertyRecord;
import org.neo4j.kernel.impl.nioneo.store.PropertyStore;
import org.neo4j.kernel.impl.nioneo.store.PropertyType;
import org.neo4j.kernel.impl.nioneo.store.Record;
import org.neo4j.kernel.impl.nioneo.store.RelationshipRecord;
import org.neo4j.kernel.impl.nioneo.store.RelationshipStore;
import org.neo4j.kernel.impl.nioneo.store.RelationshipTypeRecord;
import org.neo4j.kernel.impl.nioneo.store.RelationshipTypeStore;
import org.neo4j.kernel.impl.transaction.xaframework.LogBuffer;
import org.neo4j.kernel.impl.transaction.xaframework.XaCommand;

/**
 * Command implementations for all the commands that can be performed on a Neo
 * store.
 */
public abstract class Command extends XaCommand
{
    private final long key;

    Command( long key )
    {
        this.key = key;
    }
    
    public abstract void accept( CommandRecordVisitor visitor );

    @Override
    protected void setRecovered()
    {
        super.setRecovered();
    }

    long getKey()
    {
        return key;
    }

    @Override
    public int hashCode()
    {
        return (int) (( key >>> 32 ) ^ key );
    }

    private static void writePropertyBlock( LogBuffer buffer,
            PropertyBlock block ) throws IOException
    {
        byte blockSize = (byte) block.getSize();
        assert blockSize > 0 : blockSize + " is not a valid block size value";
        buffer.put( blockSize ); // 1
        long[] propBlockValues = block.getValueBlocks();
        for ( int k = 0; k < propBlockValues.length; k++ )
        {
            buffer.putLong( propBlockValues[k] );
        }
        /*
         * For each block we need to keep its dynamic record chain if
         * it is just created. Deleted dynamic records are in the property
         * record and dynamic records are never modified. Also, they are
         * assigned as a whole, so just checking the first should be enough.
         */
        if ( block.isLight() || !block.getValueRecords().get( 0 ).isCreated() )
        {
            /*
             *  This has to be int. If this record is not light
             *  then we have the number of DynamicRecords that follow,
             *  which is an int. We do not currently want/have a flag bit so
             *  we simplify by putting an int here always
             */
            buffer.putInt( 0 ); // 4 or
        }
        else
        {
            buffer.putInt( block.getValueRecords().size() ); // 4
            for ( int i = 0; i < block.getValueRecords().size(); i++ )
            {
                DynamicRecord dynRec = block.getValueRecords().get( i );
                writeDynamicRecord( buffer, dynRec );
            }
        }
    }
    
    static void writeDynamicRecord( LogBuffer buffer, DynamicRecord record )
        throws IOException
    {
        // id+type+in_use(byte)+nr_of_bytes(int)+next_block(long)
        if ( record.inUse() )
        {
            byte inUse = Record.IN_USE.byteValue();
            buffer.putLong( record.getId() ).putInt( record.getType() ).put(
                    inUse ).putInt( record.getLength() ).putLong(
                    record.getNextBlock() );
            byte[] data = record.getData();
            assert data != null;
            buffer.put( data );
        }
        else
        {
            byte inUse = Record.NOT_IN_USE.byteValue();
            buffer.putLong( record.getId() ).putInt( record.getType() ).put(
                inUse );
        }
    }

    static PropertyBlock readPropertyBlock( ReadableByteChannel byteChannel,
            ByteBuffer buffer ) throws IOException
    {
        PropertyBlock toReturn = new PropertyBlock();
        buffer.clear();
        buffer.limit( 1 );
        if ( byteChannel.read( buffer ) != buffer.limit() )
        {
            return null;
        }
        buffer.flip();
        byte blockSize = buffer.get(); // the size is stored in bytes // 1
        assert blockSize > 0 && blockSize % 8 == 0 : blockSize
                                                     + " is not a valid block size value";
        // Read in blocks
        buffer.clear();
        /*
         * We add 4 to avoid another limit()/read() for the DynamicRecord size
         * field later on
         */
        buffer.limit( blockSize + 4 );
        if ( byteChannel.read( buffer ) != buffer.limit() )
        {
            return null;
        }
        buffer.flip();
        long[] blocks = readLongs( buffer, blockSize / 8 );
        assert blocks.length == blockSize / 8 : blocks.length
                                                + " longs were read in while i asked for what corresponds to "
                                                + blockSize;
        assert PropertyType.getPropertyType( blocks[0], false ).calculateNumberOfBlocksUsed(
                blocks[0] ) == blocks.length : blocks.length
                                               + " is not a valid number of blocks for type "
                                               + PropertyType.getPropertyType(
                                                       blocks[0], false );
        /*
         *  Ok, now we may be ready to return, if there are no DynamicRecords. So
         *  we start building the Object
         */
        toReturn.setValueBlocks( blocks );

        /*
         * Read in existence of DynamicRecords. Remember, this has already been
         * read in the buffer with the blocks, above.
         */
        int noOfDynRecs = buffer.getInt();
        assert noOfDynRecs >= 0 : noOfDynRecs
                                  + " is not a valid value for the number of dynamic records in a property block";
        if ( noOfDynRecs != 0 )
        {
            for ( int i = 0; i < noOfDynRecs; i++ )
            {
                DynamicRecord dr = readDynamicRecord( byteChannel, buffer );
                if ( dr == null )
                {
                    return null;
                }
                dr.setCreated(); // writePropertyBlock always writes only newly
                                 // created chains
                toReturn.addValueRecord( dr );
            }
            assert toReturn.getValueRecords().size() == noOfDynRecs : "read in "
                                                                      + toReturn.getValueRecords().size()
                                                                      + " instead of the proper "
                                                                      + noOfDynRecs;
        }
        return toReturn;
    }

    static DynamicRecord readDynamicRecord( ReadableByteChannel byteChannel,
        ByteBuffer buffer ) throws IOException
    {
        // id+type+in_use(byte)+nr_of_bytes(int)+next_block(long)
        buffer.clear();
        buffer.limit( 13 );
        if ( byteChannel.read( buffer ) != buffer.limit() )
        {
            return null;
        }
        buffer.flip();
        long id = buffer.getLong();
        assert id >= 0 && id <= ( 1l << 36 ) - 1 : id
                                                  + " is not a valid dynamic record id";
        int type = buffer.getInt();
        byte inUseFlag = buffer.get();
        boolean inUse = false;
        if ( inUseFlag == Record.IN_USE.byteValue() )
        {
            inUse = true;
        }
        else if ( inUseFlag != Record.NOT_IN_USE.byteValue() )
        {
            throw new IOException( "Illegal in use flag: " + inUseFlag );
        }

        DynamicRecord record = new DynamicRecord( id );
        record.setInUse( inUse, type );
        if ( inUse )
        {
            buffer.clear();
            buffer.limit( 12 );
            if ( byteChannel.read( buffer ) != buffer.limit() )
            {
                return null;
            }
            buffer.flip();
            int nrOfBytes = buffer.getInt();
            assert nrOfBytes >= 0 && nrOfBytes < ( ( 1 << 24 ) - 1 ) : nrOfBytes
                                                                      + " is not valid for a number of bytes field of a dynamic record";
            long nextBlock = buffer.getLong();
            assert ( nextBlock >= 0 && nextBlock <= ( 1l << 36 - 1 ) )
                   || ( nextBlock == Record.NO_NEXT_BLOCK.intValue() ) : nextBlock
                                                                    + " is not valid for a next record field of a dynamic record";
            record.setNextBlock( nextBlock );
            buffer.clear();
            buffer.limit( nrOfBytes );
            if ( byteChannel.read( buffer ) != buffer.limit() )
            {
                return null;
            }
            buffer.flip();
            byte data[] = new byte[nrOfBytes];
            buffer.get( data );
            record.setData( data );
        }
        return record;
    }

    private static long[] readLongs( ByteBuffer buffer, int count )
    {
        long[] result = new long[count];
        for ( int i = 0; i < count; i++ )
        {
            result[i] = buffer.getLong();
        }
        return result;
    }

    // means the first byte of the command record was only written but second
    // (saying what type) did not get written but the file still got expanded
    private static final byte NONE = (byte) 0;

    private static final byte NODE_COMMAND = (byte) 1;
    private static final byte PROP_COMMAND = (byte) 2;
    private static final byte REL_COMMAND = (byte) 3;
    private static final byte REL_TYPE_COMMAND = (byte) 4;
    private static final byte PROP_INDEX_COMMAND = (byte) 5;
    private static final byte NEOSTORE_COMMAND = (byte) 6;

    abstract void removeFromCache( TransactionState state );

    static class NodeCommand extends Command
    {
        private final NodeRecord record;
        private final NodeStore store;

        NodeCommand( NodeStore store, NodeRecord record )
        {
            super( record.getId() );
            this.record = record;
            this.store = store;
        }
        
        @Override
        public void accept( CommandRecordVisitor visitor )
        {
            visitor.visitNode( record );
        }

        @Override
        void removeFromCache( TransactionState state )
        {
            state.removeNodeFromCache( getKey() );
        }

        @Override
        boolean isCreated()
        {
            return record.isCreated();
        }

        @Override
        boolean isDeleted()
        {
            return !record.inUse();
        }

        @Override
        public void execute()
        {
            if ( isRecovered() )
            {
                store.updateRecord( record, true );
            }
            else
            {
                store.updateRecord( record );
            }
        }

        @Override
        public String toString()
        {
            return record.toString();
        }

        @Override
        public void writeToFile( LogBuffer buffer ) throws IOException
        {
            byte inUse = record.inUse() ? Record.IN_USE.byteValue()
                : Record.NOT_IN_USE.byteValue();
            buffer.put( NODE_COMMAND );
            buffer.putLong( record.getId() );
            buffer.put( inUse );
            if ( record.inUse() )
            {
                buffer.putLong( record.getNextRel() ).putLong(
                    record.getNextProp() );
            }
        }

        public static Command readCommand( NeoStore neoStore,
            ReadableByteChannel byteChannel, ByteBuffer buffer )
            throws IOException
        {
            buffer.clear();
            buffer.limit( 9 );
            if ( byteChannel.read( buffer ) != buffer.limit() )
            {
                return null;
            }
            buffer.flip();
            long id = buffer.getLong();
            byte inUseFlag = buffer.get();
            boolean inUse = false;
            if ( inUseFlag == Record.IN_USE.byteValue() )
            {
                inUse = true;
            }
            else if ( inUseFlag != Record.NOT_IN_USE.byteValue() )
            {
                throw new IOException( "Illegal in use flag: " + inUseFlag );
            }
            NodeRecord record;
            if ( inUse )
            {
                buffer.clear();
                buffer.limit( 16 );
                if ( byteChannel.read( buffer ) != buffer.limit() )
                {
                    return null;
                }
                buffer.flip();
                record = new NodeRecord( id, buffer.getLong(), buffer.getLong() );
            }
            else record = new NodeRecord( id, Record.NO_NEXT_RELATIONSHIP.intValue(), Record.NO_NEXT_PROPERTY.intValue() );
            record.setInUse( inUse );
            return new NodeCommand( neoStore == null ? null : neoStore.getNodeStore(), record );
        }

        @Override
        public boolean equals( Object o )
        {
            if ( !(o instanceof NodeCommand) )
            {
                return false;
            }
            return getKey() == ((Command) o).getKey();
        }
    }

    static class RelationshipCommand extends Command
    {
        private final RelationshipRecord record;
        // before update stores the record as it looked before the command is executed
        private RelationshipRecord beforeUpdate;
        private final RelationshipStore store;

        RelationshipCommand( RelationshipStore store, RelationshipRecord record )
        {
            super( record.getId() );
            this.record = record;
            // the default (common) case is that the record to be written is complete and not from recovery or HA
            this.beforeUpdate = record;
            this.store = store;
        }
        
        @Override
        public void accept( CommandRecordVisitor visitor )
        {
            visitor.visitRelationship( record );
        }

        @Override
        void removeFromCache( TransactionState state )
        {
            state.removeRelationshipFromCache( getKey() );
            /*
             * If isRecovered() then beforeUpdate is the correct one UNLESS this is the second time this command
             * is executed, where it might have been actually written out to disk so the fields are already -1. So
             * we still need to check.
             * If !isRecovered() then beforeUpdate is the same as record, so we are still ok.
             * We don't check for !inUse() though because that is implicit in the call of this method.
             * The above is a hand waiving proof that the conditions that lead to the patchDeletedRelationshipNodes()
             * in the if below are the same as in RelationshipCommand.execute() so it should be safe.
             */
            if ( beforeUpdate.getFirstNode() != -1 || beforeUpdate.getSecondNode() != -1 )
            {
                state.patchDeletedRelationshipNodes( getKey(), beforeUpdate.getFirstNode(),
                        beforeUpdate.getFirstNextRel(), beforeUpdate.getSecondNode(), beforeUpdate.getSecondNextRel() );
            }
            if ( record.getFirstNode() != -1 || record.getSecondNode() != -1 )
            {
                state.removeNodeFromCache( record.getFirstNode() );
                state.removeNodeFromCache( record.getSecondNode() );
            }
        }

        @Override
        boolean isCreated()
        {
            return record.isCreated();
        }

        @Override
        boolean isDeleted()
        {
            return !record.inUse();
        }

        @Override
        public void execute()
        {
            if ( isRecovered() && !record.inUse() )
            {
                /*
                 * If read from a log (either on recovery or HA) then all the fields but for the Id are -1. If the
                 * record is deleted, then we'll need to invalidate the cache and patch the node's relationship chains.
                 * Therefore, we need to read the record from the store. This is not too expensive, since the window
                 * will be either in memory or will soon be anyway and we are just saving the write the trouble.
                 */
                beforeUpdate = store.forceGetRaw( record.getId() );
            }
            if ( isRecovered() )
            {
                store.updateRecord( record, true );
            }
            else
            {
                store.updateRecord( record );
            }
        }

        @Override
        public String toString()
        {
            return record.toString();
        }

        @Override
        public void writeToFile( LogBuffer buffer ) throws IOException
        {
            byte inUse = record.inUse() ? Record.IN_USE.byteValue()
                : Record.NOT_IN_USE.byteValue();
            buffer.put( REL_COMMAND );
            buffer.putLong( record.getId() );
            buffer.put( inUse );
            if ( record.inUse() )
            {
                buffer.putLong( record.getFirstNode() )
                        .putLong( record.getSecondNode() )
                        .putInt( record.getType() )
                        .putLong( record.getFirstPrevRel() )
                        .putLong( record.getFirstNextRel() )
                        .putLong( record.getSecondPrevRel() )
                        .putLong( record.getSecondNextRel() )
                        .putLong( record.getNextProp() )
                        ;
            }
        }

        public static Command readCommand( NeoStore neoStore,
            ReadableByteChannel byteChannel, ByteBuffer buffer )
            throws IOException
        {
            buffer.clear();
            buffer.limit( 9 );
            if ( byteChannel.read( buffer ) != buffer.limit() )
            {
                return null;
            }
            buffer.flip();
            long id = buffer.getLong();
            byte inUseFlag = buffer.get();
            boolean inUse = false;
            if ( (inUseFlag & Record.IN_USE.byteValue()) == Record.IN_USE
                .byteValue() )
            {
                inUse = true;
            }
            else if ( (inUseFlag & Record.IN_USE.byteValue()) != Record.NOT_IN_USE
                .byteValue() )
            {
                throw new IOException( "Illegal in use flag: " + inUseFlag );
            }
            RelationshipRecord record;
            if ( inUse )
            {
                buffer.clear();
                buffer.limit( 60 );
                if ( byteChannel.read( buffer ) != buffer.limit() )
                {
                    return null;
                }
                buffer.flip();
                record = new RelationshipRecord( id, buffer.getLong(), buffer
                    .getLong(), buffer.getInt() );
                record.setInUse( inUse );
                record.setFirstPrevRel( buffer.getLong() );
                record.setFirstNextRel( buffer.getLong() );
                record.setSecondPrevRel( buffer.getLong() );
                record.setSecondNextRel( buffer.getLong() );
                record.setNextProp( buffer.getLong() );
            }
            else
            {
                record = new RelationshipRecord( id, -1, -1, -1 );
                record.setInUse( false );
            }
            return new RelationshipCommand( neoStore == null ? null : neoStore.getRelationshipStore(),
                record );
        }

        @Override
        public boolean equals( Object o )
        {
            if ( !(o instanceof RelationshipCommand) )
            {
                return false;
            }
            return getKey() == ((Command) o).getKey();
        }
    }
    
    static class NeoStoreCommand extends Command
    {
        private final NeoStoreRecord record;
        private final NeoStore neoStore;

        NeoStoreCommand( NeoStore neoStore, NeoStoreRecord record )
        {
            super( -1 );
            this.neoStore = neoStore;
            this.record = record;
        }

        @Override
        boolean isCreated()
        {
            return record.isCreated();
        }

        @Override
        boolean isDeleted()
        {
            return !record.inUse();
        }

        @Override
        public void execute()
        {
            neoStore.setGraphNextProp( record.getNextProp() );
        }
        
        @Override
        public void accept( CommandRecordVisitor visitor )
        {
            visitor.visitNeoStore( record );
        }

        @Override
        void removeFromCache( TransactionState state )
        {
            // no-op
        }

        @Override
        public String toString()
        {
            return record.toString();
        }

        @Override
        public void writeToFile( LogBuffer buffer ) throws IOException
        {
            buffer.put( NEOSTORE_COMMAND ).putLong( record.getNextProp() );
        }

        public static Command readCommand( NeoStore neoStore,
                ReadableByteChannel byteChannel, ByteBuffer buffer )
                throws IOException
        {
            buffer.clear();
            buffer.limit( 8 );
            if ( byteChannel.read( buffer ) != buffer.limit() ) return null;
            buffer.flip();
            long nextProp = buffer.getLong();
            NeoStoreRecord record = new NeoStoreRecord();
            record.setNextProp( nextProp );
            return new NeoStoreCommand( neoStore, record );
        }
    }

    static class PropertyIndexCommand extends Command
    {
        private final PropertyIndexRecord record;
        private final PropertyIndexStore store;

        PropertyIndexCommand( PropertyIndexStore store,
            PropertyIndexRecord record )
        {
            super( record.getId() );
            this.record = record;
            this.store = store;
        }
        
        @Override
        public void accept( CommandRecordVisitor visitor )
        {
            visitor.visitPropertyIndex( record );
        }

        @Override
        void removeFromCache( TransactionState state )
        {
            // no-op
        }

        @Override
        boolean isCreated()
        {
            return record.isCreated();
        }

        @Override
        boolean isDeleted()
        {
            return !record.inUse();
        }

        @Override
        public void execute()
        {
            if ( isRecovered() )
            {
                store.updateRecord( record, true );
            }
            else
            {
                store.updateRecord( record );
            }
        }

        @Override
        public String toString()
        {
            return record.toString();
        }

        @Override
        public void writeToFile( LogBuffer buffer ) throws IOException
        {
            // id+in_use(byte)+count(int)+key_blockId(int)+nr_key_records(int)
            byte inUse = record.inUse() ? Record.IN_USE.byteValue()
                : Record.NOT_IN_USE.byteValue();
            buffer.put( PROP_INDEX_COMMAND );
            buffer.putInt( record.getId() );
            buffer.put( inUse );
            buffer.putInt( record.getPropertyCount() ).putInt( record.getNameId() );
            if ( record.isLight() )
            {
                buffer.putInt( 0 );
            }
            else
            {
                Collection keyRecords = record.getNameRecords();
                buffer.putInt( keyRecords.size() );
                for ( DynamicRecord keyRecord : keyRecords )
                {
                    writeDynamicRecord( buffer, keyRecord );
                }
            }
        }

        public static Command readCommand( NeoStore neoStore, ReadableByteChannel byteChannel,
            ByteBuffer buffer ) throws IOException
        {
            // id+in_use(byte)+count(int)+key_blockId(int)+nr_key_records(int)
            buffer.clear();
            buffer.limit( 17 );
            if ( byteChannel.read( buffer ) != buffer.limit() )
            {
                return null;
            }
            buffer.flip();
            int id = buffer.getInt();
            byte inUseFlag = buffer.get();
            boolean inUse = false;
            if ( (inUseFlag & Record.IN_USE.byteValue()) == Record.IN_USE
                .byteValue() )
            {
                inUse = true;
            }
            else if ( inUseFlag != Record.NOT_IN_USE.byteValue() )
            {
                throw new IOException( "Illegal in use flag: " + inUseFlag );
            }
            PropertyIndexRecord record = new PropertyIndexRecord( id );
            record.setInUse( inUse );
            record.setPropertyCount( buffer.getInt() );
            record.setNameId( buffer.getInt() );
            int nrKeyRecords = buffer.getInt();
            for ( int i = 0; i < nrKeyRecords; i++ )
            {
                DynamicRecord dr = readDynamicRecord( byteChannel, buffer );
                if ( dr == null )
                {
                    return null;
                }
                record.addNameRecord( dr );
            }
            return new PropertyIndexCommand( neoStore == null ? null : neoStore.getPropertyStore()
                .getIndexStore(), record );
        }

        @Override
        public boolean equals( Object o )
        {
            if ( !(o instanceof PropertyIndexCommand) )
            {
                return false;
            }
            return getKey() == ((Command) o).getKey();
        }
    }

    static class PropertyCommand extends Command
    {
        private final PropertyRecord record;
        private final PropertyStore store;

        PropertyCommand( PropertyStore store, PropertyRecord record )
        {
            super( record.getId() );
            this.record = record;
            this.store = store;
        }
        
        @Override
        public void accept( CommandRecordVisitor visitor )
        {
            visitor.visitProperty( record );
        }

        @Override
        void removeFromCache( TransactionState state )
        {
            long nodeId = this.getNodeId();
            long relId = this.getRelId();
            if ( nodeId != -1 )
            {
                state.removeNodeFromCache( nodeId );
            }
            else if ( relId != -1 )
            {
                state.removeRelationshipFromCache( relId );
            }
        }

        @Override
        boolean isCreated()
        {
            return record.isCreated();
        }

        @Override
        boolean isDeleted()
        {
            return !record.inUse();
        }

        @Override
        public void execute()
        {
            if ( isRecovered() )
            {
                store.updateRecord( record, true );
            }
            else
            {
                store.updateRecord( record );
            }
        }

        public long getNodeId()
        {
            return record.getNodeId();
        }

        public long getRelId()
        {
            return record.getRelId();
        }

        @Override
        public String toString()
        {
            return record.toString();
        }

        @Override
        public void writeToFile( LogBuffer buffer ) throws IOException
        {
            byte inUse = record.inUse() ? Record.IN_USE.byteValue()
                : Record.NOT_IN_USE.byteValue();
            if ( record.getRelId() != -1 )
            {
                inUse += Record.REL_PROPERTY.byteValue();
            }
            buffer.put( PROP_COMMAND );
            buffer.putLong( record.getId() ); // 8
            buffer.put( inUse ); // 1
            buffer.putLong( record.getNextProp() ).putLong(
                    record.getPrevProp() ); // 8 + 8
            long nodeId = record.getNodeId();
            long relId = record.getRelId();
            if ( nodeId != -1 )
            {
                buffer.putLong( nodeId ); // 8 or
            }
            else if ( relId != -1 )
            {
                buffer.putLong( relId ); // 8 or
            }
            else
            {
                // means this records value has not changed, only place in
                // prop chain
                buffer.putLong( -1 ); // 8
            }
            buffer.put( (byte) record.getPropertyBlocks().size() ); // 1
            for ( int i = 0; i < record.getPropertyBlocks().size(); i++ )
            {
                PropertyBlock block = record.getPropertyBlocks().get( i );
                assert block.getSize() > 0 : record + " seems kinda broken";
                writePropertyBlock( buffer, block );
            }
            buffer.putInt( record.getDeletedRecords().size() ); // 4
            for ( int i = 0; i < record.getDeletedRecords().size(); i++ )
            {
                DynamicRecord dynRec = record.getDeletedRecords().get( i );
                writeDynamicRecord( buffer, dynRec );
            }
        }

        public static Command readCommand( NeoStore neoStore,
            ReadableByteChannel byteChannel, ByteBuffer buffer )
            throws IOException
        {
            // id+in_use(byte)+type(int)+key_indexId(int)+prop_blockId(long)+
            // prev_prop_id(long)+next_prop_id(long)
            buffer.clear();
            buffer.limit( 8 + 1 + 8 + 8 + 8 );
            if ( byteChannel.read( buffer ) != buffer.limit() )
            {
                return null;
            }
            buffer.flip();

            long id = buffer.getLong(); // 8
            PropertyRecord record = new PropertyRecord( id );
            byte inUseFlag = buffer.get(); // 1
            long nextProp = buffer.getLong(); // 8
            long prevProp = buffer.getLong(); // 8
            record.setNextProp( nextProp );
            record.setPrevProp( prevProp );
            boolean inUse = false;
            if ( ( inUseFlag & Record.IN_USE.byteValue() ) == Record.IN_USE.byteValue() )
            {
                inUse = true;
            }
            boolean nodeProperty = true;
            if ( ( inUseFlag & Record.REL_PROPERTY.byteValue() ) == Record.REL_PROPERTY.byteValue() )
            {
                nodeProperty = false;
            }
            long primitiveId = buffer.getLong(); // 8
            if ( primitiveId != -1 && nodeProperty )
            {
                record.setNodeId( primitiveId );
            }
            else if ( primitiveId != -1 )
            {
                record.setRelId( primitiveId );
            }
            buffer.clear();
            buffer.limit( 1 );
            if ( byteChannel.read( buffer ) != buffer.limit() )
            {
                return null;
            }
            buffer.flip();
            int nrPropBlocks = buffer.get(); // 1
            assert nrPropBlocks >= 0;
            if ( nrPropBlocks > 0 )
            {
                record.setInUse( true );
            }
            while ( nrPropBlocks-- > 0 )
            {
                PropertyBlock block = readPropertyBlock( byteChannel, buffer );
                if ( block == null )
                {
                    return null;
                }
                record.addPropertyBlock( block );
            }
            // Time to read in the deleted dynamic records
            buffer.clear();
            buffer.limit( 4 );
            if ( byteChannel.read( buffer ) != buffer.limit() )
            {
                return null;
            }
            buffer.flip();
            int deletedRecords = buffer.getInt(); // 4
            assert deletedRecords >= 0;
            while ( deletedRecords-- > 0 )
            {
                DynamicRecord read = readDynamicRecord( byteChannel, buffer );
                if ( read == null )
                {
                    return null;
                }
                assert !read.inUse() : read + " is kinda weird";
                record.addDeletedRecord( read );
            }

            if ( ( inUse && !record.inUse() ) || ( !inUse && record.inUse() ) )
            {
                throw new IllegalStateException( "Weird, inUse was read in as "
                                                 + inUse
                                                 + " but the record is "
                                                 + record );
            }
            return new PropertyCommand( neoStore == null ? null
                    : neoStore.getPropertyStore(), record );
        }

        @Override
        public boolean equals( Object o )
        {
            if ( !(o instanceof PropertyCommand) )
            {
                return false;
            }
            return getKey() == ((Command) o).getKey();
        }
    }

    static class RelationshipTypeCommand extends Command
    {
        private final RelationshipTypeRecord record;
        private final RelationshipTypeStore store;

        RelationshipTypeCommand( RelationshipTypeStore store,
            RelationshipTypeRecord record )
        {
            super( record.getId() );
            this.record = record;
            this.store = store;
        }
        
        @Override
        public void accept( CommandRecordVisitor visitor )
        {
            visitor.visitRelationshipType( record );
        }

        @Override
        void removeFromCache( TransactionState state )
        {
            // no-op
        }

        @Override
        boolean isCreated()
        {
            return record.isCreated();
        }

        @Override
        boolean isDeleted()
        {
            return !record.inUse();
        }

        @Override
        public void execute()
        {
            if ( isRecovered() )
            {
                store.updateRecord( record, true );
            }
            else
            {
                store.updateRecord( record );
            }
        }

        @Override
        public String toString()
        {
            return record.toString();
        }

        @Override
        public void writeToFile( LogBuffer buffer ) throws IOException
        {
            // id+in_use(byte)+type_blockId(int)+nr_type_records(int)
            byte inUse = record.inUse() ? Record.IN_USE.byteValue()
                : Record.NOT_IN_USE.byteValue();
            buffer.put( REL_TYPE_COMMAND );
            buffer.putInt( record.getId() ).put( inUse ).putInt( record.getNameId() );

            Collection typeRecords = record.getNameRecords();
            buffer.putInt( typeRecords.size() );
            for ( DynamicRecord typeRecord : typeRecords )
            {
                writeDynamicRecord( buffer, typeRecord );
            }
        }

        public static Command readCommand( NeoStore neoStore,
            ReadableByteChannel byteChannel, ByteBuffer buffer )
            throws IOException
        {
            // id+in_use(byte)+type_blockId(int)+nr_type_records(int)
            buffer.clear();
            buffer.limit( 13 );
            if ( byteChannel.read( buffer ) != buffer.limit() )
            {
                return null;
            }
            buffer.flip();
            int id = buffer.getInt();
            byte inUseFlag = buffer.get();
            boolean inUse = false;
            if ( (inUseFlag & Record.IN_USE.byteValue()) ==
                Record.IN_USE.byteValue() )
            {
                inUse = true;
            }
            else if ( inUseFlag != Record.NOT_IN_USE.byteValue() )
            {
                throw new IOException( "Illegal in use flag: " + inUseFlag );
            }
            RelationshipTypeRecord record = new RelationshipTypeRecord( id );
            record.setInUse( inUse );
            record.setNameId( buffer.getInt() );
            int nrTypeRecords = buffer.getInt();
            for ( int i = 0; i < nrTypeRecords; i++ )
            {
                DynamicRecord dr = readDynamicRecord( byteChannel, buffer );
                if ( dr == null )
                {
                    return null;
                }
                record.addNameRecord( dr );
            }
            return new RelationshipTypeCommand(
                    neoStore == null ? null : neoStore.getRelationshipTypeStore(), record );
        }

        @Override
        public boolean equals( Object o )
        {
            if ( !(o instanceof RelationshipTypeCommand) )
            {
                return false;
            }
            return getKey() == ((Command) o).getKey();
        }
    }

    public static Command readCommand( NeoStore neoStore, ReadableByteChannel byteChannel,
        ByteBuffer buffer ) throws IOException
    {
        buffer.clear();
        buffer.limit( 1 );
        if ( byteChannel.read( buffer ) != buffer.limit() )
        {
            return null;
        }
        buffer.flip();
        byte commandType = buffer.get();
        switch ( commandType )
        {
            case NODE_COMMAND:
                return NodeCommand.readCommand( neoStore, byteChannel, buffer );
            case PROP_COMMAND:
                return PropertyCommand.readCommand( neoStore, byteChannel,
                    buffer );
            case PROP_INDEX_COMMAND:
                return PropertyIndexCommand.readCommand( neoStore, byteChannel,
                    buffer );
            case REL_COMMAND:
                return RelationshipCommand.readCommand( neoStore, byteChannel,
                    buffer );
            case REL_TYPE_COMMAND:
                return RelationshipTypeCommand.readCommand( neoStore,
                    byteChannel, buffer );
            case NEOSTORE_COMMAND:
                return NeoStoreCommand.readCommand( neoStore, byteChannel, buffer );
            case NONE: return null;
            default:
                throw new IOException( "Unknown command type[" + commandType
                    + "]" );
        }
    }

    abstract boolean isCreated();

    abstract boolean isDeleted();
}




© 2015 - 2025 Weber Informatics LLC | Privacy Policy