/** * Copyright (c) 2002-2013 "Neo Technology," * Network Engine for Objects in Lund AB [http://neotechnology.com] * * This file is part of Neo4j. * * Neo4j is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation, either version 3 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program. If not, see <http://www.gnu.org/licenses/>. */ package org.neo4j.kernel.impl.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<String, String> config; CreateCommand( byte indexNameId, byte entityType, Map<String, String> config ) { super( CREATE_COMMAND, indexNameId, entityType, 0L, (byte)0, null ); this.config = config; } public Map<String, String> getConfig() { return config; } @Override public void writeToFile( LogBuffer buffer ) throws IOException { writeHeader( buffer ); buffer.putShort( (short)config.size() ); for ( Map.Entry<String, String> 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<String, Byte> indexNames = IndexDefineCommand.readMap( channel, buffer ); Map<String, Byte> keys = IndexDefineCommand.readMap( channel, buffer ); if ( indexNames == null || keys == null ) return null; return new IndexDefineCommand( indexNames, keys ); case CREATE_COMMAND: Map<String, String> 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 ); } }