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

org.neo4j.kernel.impl.index.IndexCommand Maven / Gradle / Ivy

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

import static org.neo4j.kernel.impl.util.IoPrimitiveUtils.read2bMap;
import static org.neo4j.kernel.impl.util.IoPrimitiveUtils.read3bLengthAndString;
import static org.neo4j.kernel.impl.util.IoPrimitiveUtils.readBytes;
import static org.neo4j.kernel.impl.util.IoPrimitiveUtils.readDouble;
import static org.neo4j.kernel.impl.util.IoPrimitiveUtils.readFloat;
import static org.neo4j.kernel.impl.util.IoPrimitiveUtils.readInt;
import static org.neo4j.kernel.impl.util.IoPrimitiveUtils.readLong;
import static org.neo4j.kernel.impl.util.IoPrimitiveUtils.readShort;
import static org.neo4j.kernel.impl.util.IoPrimitiveUtils.write2bLengthAndString;
import static org.neo4j.kernel.impl.util.IoPrimitiveUtils.write3bLengthAndString;

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

import org.neo4j.graphdb.index.Index;
import org.neo4j.kernel.impl.transaction.xaframework.LogBuffer;
import org.neo4j.kernel.impl.transaction.xaframework.XaCommand;

/**
 * Created from {@link IndexDefineCommand} or read from a logical log.
 * Contains all the different types of commands that an {@link Index} need
 * to support.
 */
public abstract class IndexCommand extends XaCommand
{
    static final byte DEFINE_COMMAND = (byte) 0;
    static final byte ADD_COMMAND = (byte) 1;
    static final byte ADD_RELATIONSHIP_COMMAND = (byte) 2;
    static final byte REMOVE_COMMAND = (byte) 3;
    static final byte DELETE_COMMAND = (byte) 4;
    static final byte CREATE_COMMAND = (byte) 5;
    
    public static final byte NODE = (byte) 0;
    public static final byte RELATIONSHIP = (byte) 1;
    
    private static final byte VALUE_TYPE_NULL = (byte) 0;
    private static final byte VALUE_TYPE_SHORT = (byte) 1;
    private static final byte VALUE_TYPE_INT = (byte) 2;
    private static final byte VALUE_TYPE_LONG = (byte) 3;
    private static final byte VALUE_TYPE_FLOAT = (byte) 4;
    private static final byte VALUE_TYPE_DOUBLE = (byte) 5;
    private static final byte VALUE_TYPE_STRING = (byte) 6;
    
    private final byte commandType;
    private final byte indexNameId;
    private final byte entityType;
    private final long entityId;
    private final byte keyId;
    private final byte valueType;
    private final Object value;
    
    IndexCommand( byte commandType, byte indexNameId, byte entityType, long entityId, byte keyId, Object value )
    {
        this.commandType = commandType;
        this.indexNameId = indexNameId;
        this.entityType = entityType;
        this.entityId = entityId;
        this.keyId = keyId;
        this.value = value;
        this.valueType = valueTypeOf( value );
    }
    
    public byte getIndexNameId()
    {
        return indexNameId;
    }
    
    public byte getEntityType()
    {
        return entityType;
    }
    
    public long getEntityId()
    {
        return entityId;
    }
    
    public byte getKeyId()
    {
        return keyId;
    }
    
    public Object getValue()
    {
        return value;
    }
    
    @Override
    public void execute()
    {
    }
    
    @Override
    public void writeToFile( LogBuffer buffer ) throws IOException
    {
        /* c: commandType
         * e: entityType
         * n: indexNameId
         * k: keyId
         * i: entityId
         * v: value type
         * u: value
         * x: 0=entityId needs 4b, 1=entityId needs 8b
         * y: 0=startNode needs 4b, 1=startNode needs 8b
         * z: 0=endNode needs 4b, 1=endNode needs 8b
         * 
         * [cccv,vvex][yznn,nnnn][kkkk,kkkk]
         * [iiii,iiii] x 4 or 8
         * (either string value)
         * [llll,llll][llll,llll][llll,llll][string chars...]
         * (numeric value)
         * [uuuu,uuuu] x 2-8 (depending on value type)
         */
        
        writeHeader( buffer );
        putIntOrLong( buffer, entityId );
        
        // Value
        switch ( valueType )
        {
        case VALUE_TYPE_STRING: write3bLengthAndString( buffer, value.toString() ); break;
        case VALUE_TYPE_SHORT: buffer.putShort( ((Number) value).shortValue() ); break;
        case VALUE_TYPE_INT: buffer.putInt( ((Number) value).intValue() ); break;
        case VALUE_TYPE_LONG: buffer.putLong( ((Number) value).longValue() ); break;
        case VALUE_TYPE_FLOAT: buffer.putFloat( ((Number) value).floatValue() ); break;
        case VALUE_TYPE_DOUBLE: buffer.putDouble( ((Number) value).doubleValue() ); break;
        case VALUE_TYPE_NULL: break;
        default: throw new RuntimeException( "Unknown value type " + valueType );
        }
    }

    protected void writeHeader( LogBuffer buffer ) throws IOException
    {
        buffer.put( (byte)((commandType<<5) | (valueType<<2) | (entityType<<1) | (needsLong( entityId ))) );
        buffer.put( (byte)((startNodeNeedsLong()<<7) | (endNodeNeedsLong()<<6) | (indexNameId)) );
        buffer.put( keyId );
    }
    
    protected static void putIntOrLong( LogBuffer buffer, long id ) throws IOException
    {
        if ( needsLong( id ) == 1 )
        {
            buffer.putLong( id );
        }
        else
        {
            buffer.putInt( (int)id );
        }
    }

    protected static byte needsLong( long value )
    {
        return value > Integer.MAX_VALUE ? (byte)1 : (byte)0;
    }
    
    protected byte startNodeNeedsLong()
    {
        return 0;
    }
    
    protected byte endNodeNeedsLong()
    {
        return 0;
    }
    
    private static byte valueTypeOf( Object value )
    {
        byte valueType = 0;
        if ( value == null )
        {
            valueType = VALUE_TYPE_NULL;
        }
        else if ( value instanceof Number )
        {
            if ( value instanceof Float )
            {
                valueType = VALUE_TYPE_FLOAT;
            }
            else if ( value instanceof Double )
            {
                valueType = VALUE_TYPE_DOUBLE;
            }
            else if ( value instanceof Long )
            {
                valueType = VALUE_TYPE_LONG;
            }
            else if ( value instanceof Short )
            {
                valueType = VALUE_TYPE_SHORT;
            }
            else
            {
                valueType = VALUE_TYPE_INT;
            }
        }
        else
        {
            valueType = VALUE_TYPE_STRING;
        }
        return valueType;
    }
    
    public boolean isConsideredNormalWriteCommand()
    {
        return true;
    }
    
    public static class AddCommand extends IndexCommand
    {
        AddCommand( byte indexNameId, byte entityType, long entityId, byte keyId, Object value )
        {
            super( ADD_COMMAND, indexNameId, entityType, entityId, keyId, value );
        }
    }
    
    public static class AddRelationshipCommand extends IndexCommand
    {
        private final long startNode;
        private final long endNode;

        AddRelationshipCommand( byte indexNameId, byte entityType, long entityId, byte keyId,
                Object value, long startNode, long endNode )
        {
            super( ADD_RELATIONSHIP_COMMAND, indexNameId, entityType, entityId, keyId, value );
            this.startNode = startNode;
            this.endNode = endNode;
        }
        
        public long getStartNode()
        {
            return startNode;
        }
        
        public long getEndNode()
        {
            return endNode;
        }
        
        @Override
        protected byte startNodeNeedsLong()
        {
            return needsLong( startNode );
        }
        
        @Override
        protected byte endNodeNeedsLong()
        {
            return needsLong( endNode );
        }
        
        @Override
        public void writeToFile( LogBuffer buffer ) throws IOException
        {
            super.writeToFile( buffer );
            putIntOrLong( buffer, startNode );
            putIntOrLong( buffer, endNode );
        }
        
        @Override
        public boolean equals( Object obj )
        {
            if ( !super.equals( obj ) )
            {
                return false;
            }
            AddRelationshipCommand other = (AddRelationshipCommand) obj;
            return startNode == other.startNode && endNode == other.endNode;
        }
    }
    
    public static class RemoveCommand extends IndexCommand
    {
        RemoveCommand( byte indexNameId, byte entityType, long entityId, byte keyId, Object value )
        {
            super( REMOVE_COMMAND, indexNameId, entityType, entityId, keyId, value );
        }
    }

    public static class DeleteCommand extends IndexCommand
    {
        DeleteCommand( byte indexNameId, byte entityType )
        {
            super( DELETE_COMMAND, indexNameId, entityType, 0L, (byte)0, null );
        }
        
        @Override
        public void writeToFile( LogBuffer buffer ) throws IOException
        {
            writeHeader( buffer );
        }
        
        @Override
        public boolean isConsideredNormalWriteCommand()
        {
            return false;
        }
    }
    
    public static class CreateCommand extends IndexCommand
    {
        private final Map config;

        CreateCommand( byte indexNameId, byte entityType, Map config )
        {
            super( CREATE_COMMAND, indexNameId, entityType, 0L, (byte)0, null ); 
            this.config = config;
        }
        
        public Map getConfig()
        {
            return config;
        }

        @Override
        public void writeToFile( LogBuffer buffer ) throws IOException
        {
            writeHeader( buffer );
            buffer.putShort( (short)config.size() );
            for ( Map.Entry entry : config.entrySet() )
            {
                write2bLengthAndString( buffer, entry.getKey() );
                write2bLengthAndString( buffer, entry.getValue() );
            }
        }
        
        @Override
        public boolean isConsideredNormalWriteCommand()
        {
            return false;
        }
        
        @Override
        public boolean equals( Object obj )
        {
            return super.equals( obj ) && config.equals( ((CreateCommand)obj).config );
        }
    }
    
    private static Object readValue( byte valueType, ReadableByteChannel channel, ByteBuffer buffer )
            throws IOException
    {
        switch ( valueType )
        {
        case VALUE_TYPE_NULL: return null;
        case VALUE_TYPE_SHORT: return readShort( channel, buffer );
        case VALUE_TYPE_INT: return readInt( channel, buffer );
        case VALUE_TYPE_LONG: return readLong( channel, buffer );
        case VALUE_TYPE_FLOAT: return readFloat( channel, buffer );
        case VALUE_TYPE_DOUBLE: return readDouble( channel, buffer );
        case VALUE_TYPE_STRING: return read3bLengthAndString( channel, buffer );
        default: throw new RuntimeException( "Unknown value type " + valueType );
        }
    }
    
    public static XaCommand readCommand( ReadableByteChannel channel, ByteBuffer buffer ) throws IOException
    {
        byte[] headerBytes = readBytes( channel, new byte[3] );
        if ( headerBytes == null ) return null;
        
        byte commandType = (byte)((headerBytes[0] & 0xE0) >> 5);
        byte valueType = (byte)((headerBytes[0] & 0x1C) >> 2);
        byte entityType = (byte)((headerBytes[0] & 0x2) >> 1);
        boolean entityIdNeedsLong = (headerBytes[0] & 0x1) > 0;
        byte indexNameId = (byte)(headerBytes[1] & 0x3F);
        byte keyId = headerBytes[2];
        
        switch ( commandType )
        {
        case DEFINE_COMMAND:
            Map indexNames = IndexDefineCommand.readMap( channel, buffer );
            Map keys = IndexDefineCommand.readMap( channel, buffer );
            if ( indexNames == null || keys == null ) return null;
            return new IndexDefineCommand( indexNames, keys );
        case CREATE_COMMAND:
            Map config = read2bMap( channel, buffer );
            if ( config == null ) return null;
            return new CreateCommand( indexNameId, entityType, config );
        case DELETE_COMMAND:
            return new DeleteCommand( indexNameId, entityType );
        case ADD_COMMAND: case REMOVE_COMMAND: case ADD_RELATIONSHIP_COMMAND:
            Number entityId = entityIdNeedsLong ? (Number)readLong( channel, buffer ) : (Number)readInt( channel, buffer );
            if ( entityId == null ) return null;
            Object value = readValue( valueType, channel, buffer );
            if ( valueType != VALUE_TYPE_NULL && value == null ) return null;
            if ( commandType == ADD_COMMAND )
            {
                return new AddCommand( indexNameId, entityType, entityId.longValue(), keyId, value );
            }
            else if ( commandType == REMOVE_COMMAND )
            {
                return new RemoveCommand( indexNameId, entityType, entityId.longValue(), keyId, value );
            }
            else
            {
                boolean startNodeNeedsLong = (headerBytes[1] & 0x8) > 0;
                boolean endNodeNeedsLong = (headerBytes[1] & 0x40) > 0;
                Number startNode = startNodeNeedsLong ? (Number)readLong( channel, buffer ) : (Number)readInt( channel, buffer );
                Number endNode = endNodeNeedsLong ? (Number)readLong( channel, buffer ) : (Number)readInt( channel, buffer );
                if ( startNode == null || endNode == null ) return null;
                return new AddRelationshipCommand( indexNameId, entityType, entityId.longValue(),
                        keyId, value, startNode.longValue(), endNode.longValue() );
            }
        default: throw new RuntimeException( "Unknown command type " + commandType );
        }
    }
    
    @Override
    public boolean equals( Object obj )
    {
        IndexCommand other = (IndexCommand) obj;
        boolean equals = commandType == other.commandType &&
                entityType == other.entityType &&
                indexNameId == other.indexNameId &&
                keyId == other.keyId &&
                valueType == other.valueType;
        if ( !equals )
        {
            return false;
        }
        
        return value == null ? other.value == null : value.equals( other.value );
    }
}




© 2015 - 2025 Weber Informatics LLC | Privacy Policy