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

org.neo4j.bolt.v1.messaging.Neo4jPackV1 Maven / Gradle / Ivy

Go to download

The core of Neo4j Bolt Protocol, this contains the state machine for Bolt sessions.

There is a newer version: 5.23.0
Show newest version
/*
 * Copyright (c) 2002-2018 "Neo4j,"
 * Neo4j Sweden AB [http://neo4j.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.bolt.v1.messaging;

import java.io.IOException;
import java.time.LocalDate;
import java.time.LocalDateTime;
import java.time.LocalTime;
import java.time.OffsetTime;
import java.time.ZonedDateTime;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;

import org.neo4j.bolt.messaging.StructType;
import org.neo4j.bolt.v1.packstream.PackInput;
import org.neo4j.bolt.v1.packstream.PackOutput;
import org.neo4j.bolt.v1.packstream.PackStream;
import org.neo4j.bolt.v1.packstream.PackType;
import org.neo4j.collection.primitive.PrimitiveLongIntKeyValueArray;
import org.neo4j.kernel.api.exceptions.Status;
import org.neo4j.values.AnyValue;
import org.neo4j.values.AnyValueWriter;
import org.neo4j.values.storable.CoordinateReferenceSystem;
import org.neo4j.values.storable.TextArray;
import org.neo4j.values.storable.TextValue;
import org.neo4j.values.storable.Values;
import org.neo4j.values.virtual.ListValue;
import org.neo4j.values.virtual.MapValue;
import org.neo4j.values.virtual.NodeValue;
import org.neo4j.values.virtual.RelationshipValue;
import org.neo4j.values.virtual.VirtualValues;

import static org.neo4j.bolt.v1.packstream.PackStream.UNKNOWN_SIZE;
import static org.neo4j.values.storable.Values.byteArray;

/**
 * Extended PackStream packer and unpacker classes for working
 * with Neo4j-specific data types, represented as structures.
 */
public class Neo4jPackV1 implements Neo4jPack
{
    public static final long VERSION = 1;

    public static final byte NODE = 'N';
    public static final int NODE_SIZE = 3;

    public static final byte RELATIONSHIP = 'R';
    public static final int RELATIONSHIP_SIZE = 5;

    public static final byte UNBOUND_RELATIONSHIP = 'r';
    public static final int UNBOUND_RELATIONSHIP_SIZE = 3;

    public static final byte PATH = 'P';
    public static final int PATH_SIZE = 3;

    @Override
    public Neo4jPack.Packer newPacker( PackOutput output )
    {
        return new PackerV1( output );
    }

    @Override
    public Neo4jPack.Unpacker newUnpacker( PackInput input )
    {
        return new UnpackerV1( input );
    }

    @Override
    public long version()
    {
        return VERSION;
    }

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

    protected static class PackerV1 extends PackStream.Packer implements AnyValueWriter, Neo4jPack.Packer
    {
        private static final int INITIAL_PATH_CAPACITY = 500;
        private static final int NO_SUCH_ID = -1;
        private final PrimitiveLongIntKeyValueArray nodeIndexes =
                new PrimitiveLongIntKeyValueArray( INITIAL_PATH_CAPACITY + 1 );
        private final PrimitiveLongIntKeyValueArray relationshipIndexes =
                new PrimitiveLongIntKeyValueArray( INITIAL_PATH_CAPACITY );

        protected PackerV1( PackOutput output )
        {
            super( output );
        }

        @Override
        public void pack( AnyValue value ) throws IOException
        {
            value.writeTo( this );
        }

        @Override
        public void writeNodeReference( long nodeId )
        {
            throw new UnsupportedOperationException( "Cannot write a raw node reference" );
        }

        @Override
        public void writeNode( long nodeId, TextArray labels, MapValue properties ) throws IOException
        {
            packStructHeader( NODE_SIZE, NODE );
            pack( nodeId );
            packListHeader( labels.length() );
            for ( int i = 0; i < labels.length(); i++ )
            {
                labels.value( i ).writeTo( this );
            }
            properties.writeTo( this );
        }

        @Override
        public void writeRelationshipReference( long relationshipId )
        {
            throw new UnsupportedOperationException( "Cannot write a raw relationship reference" );
        }

        @Override
        public void writeRelationship( long relationshipId, long startNodeId, long endNodeId, TextValue type, MapValue properties )
                throws IOException
        {
            packStructHeader( RELATIONSHIP_SIZE, RELATIONSHIP );
            pack( relationshipId );
            pack( startNodeId );
            pack( endNodeId );
            type.writeTo( this );
            properties.writeTo( this );
        }

        @Override
        public void beginMap( int size ) throws IOException
        {
            packMapHeader( size );
        }

        @Override
        public void endMap()
        {
            //do nothing
        }

        @Override
        public void beginList( int size ) throws IOException
        {
            packListHeader( size );
        }

        @Override
        public void endList()
        {
            //do nothing
        }

        @Override
        public void writePath( NodeValue[] nodes, RelationshipValue[] relationships ) throws IOException
        {
            //A path is serialized in the following form
            // Given path: (a {id: 42})-[r1 {id: 10}]->(b {id: 43})<-[r1 {id: 11}]-(c {id: 44})
            //The serialization will look like:
            //
            // {
            //    [a, b, c]
            //    [r1, r2]
            //    [1, 1, -2, 2]
            // }
            // The first list contains all nodes where the first node (a) is guaranteed to be the start node of
            // the path
            // The second list contains all edges of the path
            // The third list defines the path order, where every other item specifies the offset into the
            // relationship and node list respectively. Since all paths is guaranteed to start with a 0, meaning
            // that
            // a is the start node in this case, those are excluded. So the first integer in the array refers to the
            // position
            // in the relationship array (1 indexed where sign denotes direction) and the second one refers to
            // the offset
            // into the
            // node list (zero indexed) and so on.
            packStructHeader( PATH_SIZE, PATH );

            writeNodesForPath( nodes );
            writeRelationshipsForPath( relationships );

            packListHeader( 2 * relationships.length );
            if ( relationships.length == 0 )
            {
                return;
            }

            NodeValue node = nodes[0];
            for ( int i = 1; i <= 2 * relationships.length; i++ )
            {
                if ( i % 2 == 0 )
                {
                    node = nodes[i / 2];
                    int index = nodeIndexes.getOrDefault( node.id(), NO_SUCH_ID );
                    pack( index );
                }
                else
                {
                    RelationshipValue r = relationships[i / 2];
                    int index = relationshipIndexes.getOrDefault( r.id(), NO_SUCH_ID );

                    if ( node.id() == r.startNode().id() )
                    {
                        pack( index );
                    }
                    else
                    {
                        pack( -index );
                    }
                }

            }
        }

        private void writeNodesForPath( NodeValue[] nodes ) throws IOException
        {
            nodeIndexes.reset( nodes.length );
            for ( NodeValue node : nodes )
            {
                nodeIndexes.putIfAbsent( node.id(), nodeIndexes.size() );
            }

            int size = nodeIndexes.size();
            packListHeader( size );
            if ( size > 0 )
            {
                NodeValue node = nodes[0];
                for ( long id : nodeIndexes.keys() )
                {
                    int i = 1;
                    while ( node.id() != id )
                    {
                        node = nodes[i++];
                    }
                    node.writeTo( this );
                }
            }
        }

        private void writeRelationshipsForPath( RelationshipValue[] relationships ) throws IOException
        {
            relationshipIndexes.reset( relationships.length );
            for ( RelationshipValue node : relationships )
            {
                // relationship indexes are one-based
                relationshipIndexes.putIfAbsent( node.id(), relationshipIndexes.size() + 1 );
            }

            int size = relationshipIndexes.size();
            packListHeader( size );
            if ( size > 0 )
            {
                {
                    RelationshipValue edge = relationships[0];
                    for ( long id : relationshipIndexes.keys() )
                    {
                        int i = 1;
                        while ( edge.id() != id )
                        {
                            edge = relationships[i++];
                        }
                        //Note that we are not doing relationship.writeTo(this) here since the serialization protocol
                        //requires these to be _unbound relationships_, thus relationships without any start node nor
                        // end node.
                        packStructHeader( UNBOUND_RELATIONSHIP_SIZE, UNBOUND_RELATIONSHIP );
                        pack( edge.id() );
                        edge.type().writeTo( this );
                        edge.properties().writeTo( this );
                    }
                }
            }
        }

        @Override
        public void writePoint( CoordinateReferenceSystem crs, double[] coordinate ) throws IOException
        {
            throw new BoltIOException( Status.Request.Invalid, "Point is not yet supported as a return type in Bolt" );
        }

        @Override
        public void writeDuration( long months, long days, long seconds, int nanos ) throws IOException
        {
            throw new BoltIOException( Status.Request.Invalid, "Duration is not yet supported as a return type in Bolt" );
        }

        @Override
        public void writeDate( LocalDate localDate ) throws IOException
        {
            throw new BoltIOException( Status.Request.Invalid, "Date is not yet supported as a return type in Bolt" );
        }

        @Override
        public void writeLocalTime( LocalTime localTime ) throws IOException
        {
            throw new BoltIOException( Status.Request.Invalid, "LocalTime is not yet supported as a return type in Bolt" );
        }

        @Override
        public void writeTime( OffsetTime offsetTime ) throws IOException
        {
            throw new BoltIOException( Status.Request.Invalid, "Time is not yet supported as a return type in Bolt" );
        }

        @Override
        public void writeLocalDateTime( LocalDateTime localDateTime ) throws IOException
        {
            throw new BoltIOException( Status.Request.Invalid, "LocalDateTime is not yet supported as a return type in Bolt" );
        }

        @Override
        public void writeDateTime( ZonedDateTime zonedDateTime ) throws IOException
        {
            throw new BoltIOException( Status.Request.Invalid, "DateTime is not yet supported as a return type in Bolt" );
        }

        @Override
        public void writeNull() throws IOException
        {
            packNull();
        }

        @Override
        public void writeBoolean( boolean value ) throws IOException
        {
            pack( value );
        }

        @Override
        public void writeInteger( byte value ) throws IOException
        {
            pack( value );
        }

        @Override
        public void writeInteger( short value ) throws IOException
        {
            pack( value );
        }

        @Override
        public void writeInteger( int value ) throws IOException
        {
            pack( value );
        }

        @Override
        public void writeInteger( long value ) throws IOException
        {
            pack( value );
        }

        @Override
        public void writeFloatingPoint( float value ) throws IOException
        {
            pack( value );
        }

        @Override
        public void writeFloatingPoint( double value ) throws IOException
        {
            pack( value );
        }

        @Override
        public void writeUTF8( byte[] bytes, int offset, int length ) throws IOException
        {
            packUTF8(bytes, offset, length);
        }

        @Override
        public void writeString( String value ) throws IOException
        {
            pack( value );
        }

        @Override
        public void writeString( char value ) throws IOException
        {
            pack( value );
        }

        @Override
        public void beginArray( int size, ArrayType arrayType ) throws IOException
        {
            switch ( arrayType )
            {
            case BYTE:
                packBytesHeader( size );
                break;
            default:
                packListHeader( size );
            }

        }

        @Override
        public void endArray()
        {
            //Do nothing
        }

        @Override
        public void writeByteArray( byte[] value ) throws IOException
        {
            pack( value );
        }
    }

    protected static class UnpackerV1 extends PackStream.Unpacker implements Neo4jPack.Unpacker
    {
        protected UnpackerV1( PackInput input )
        {
            super( input );
        }

        @Override
        public AnyValue unpack() throws IOException
        {
            PackType valType = peekNextType();
            switch ( valType )
            {
            case BYTES:
                return byteArray( unpackBytes() );
            case STRING:
                return Values.utf8Value( unpackUTF8() );
            case INTEGER:
                return Values.longValue( unpackLong() );
            case FLOAT:
                return Values.doubleValue( unpackDouble() );
            case BOOLEAN:
                return Values.booleanValue( unpackBoolean() );
            case NULL:
                // still need to move past the null value
                unpackNull();
                return Values.NO_VALUE;
            case LIST:
            {
                return unpackList();
            }
            case MAP:
            {
                return unpackMap();
            }
            case STRUCT:
            {
                long size = unpackStructHeader();
                char signature = unpackStructSignature();
                return unpackStruct( signature, size );
            }
            case END_OF_STREAM:
            {
                unpackEndOfStream();
                return null;
            }
            default:
                throw new BoltIOException( Status.Request.InvalidFormat, "Unknown value type: " + valType );
            }
        }

        ListValue unpackList() throws IOException
        {
            int size = (int) unpackListHeader();
            if ( size == 0 )
            {
                return VirtualValues.EMPTY_LIST;
            }
            else if ( size == UNKNOWN_SIZE )
            {
                List list = new ArrayList<>();
                boolean more = true;
                while ( more )
                {
                    PackType keyType = peekNextType();
                    switch ( keyType )
                    {
                    case END_OF_STREAM:
                        unpack();
                        more = false;
                        break;
                    default:
                        list.add( unpack() );
                    }
                }
                return VirtualValues.list( list.toArray( new AnyValue[0] ) );
            }
            else
            {
                AnyValue[] values = new AnyValue[size];
                for ( int i = 0; i < size; i++ )
                {
                    values[i] = unpack();
                }
                return VirtualValues.list( values );
            }
        }

        protected AnyValue unpackStruct( char signature, long size ) throws IOException
        {
            StructType structType = StructType.valueOf( signature );
            if ( structType == null )
            {
                throw new BoltIOException( Status.Request.InvalidFormat,
                        String.format( "Struct types of 0x%s are not recognized.", Integer.toHexString( signature ) ) );
            }

            throw new BoltIOException( Status.Statement.TypeError,
                    String.format( "%s values cannot be unpacked with this version of bolt.", structType.description() ) );
        }

        @Override
        public MapValue unpackMap() throws IOException
        {
            int size = (int) unpackMapHeader();
            if ( size == 0 )
            {
                return VirtualValues.EMPTY_MAP;
            }
            Map map;
            if ( size == UNKNOWN_SIZE )
            {
                map = new HashMap<>();
                boolean more = true;
                while ( more )
                {
                    PackType keyType = peekNextType();
                    String key;
                    AnyValue val;
                    switch ( keyType )
                    {
                    case END_OF_STREAM:
                        unpack();
                        more = false;
                        break;
                    case STRING:
                        key = unpackString();
                        val = unpack();
                        if ( map.put( key, val ) != null )
                        {
                            throw new BoltIOException( Status.Request.Invalid, "Duplicate map key `" + key + "`." );
                        }
                        break;
                    case NULL:
                        throw new BoltIOException( Status.Request.Invalid, "Value `null` is not supported as key in maps, must be a non-nullable string." );
                    default:
                        throw new BoltIOException( Status.Request.InvalidFormat, "Bad key type: " + keyType );
                    }
                }
            }
            else
            {
                map = new HashMap<>( size, 1 );
                for ( int i = 0; i < size; i++ )
                {
                    PackType keyType = peekNextType();
                    String key;
                    switch ( keyType )
                    {
                    case NULL:
                        throw new BoltIOException( Status.Request.Invalid, "Value `null` is not supported as key in maps, must be a non-nullable string." );
                    case STRING:
                        key = unpackString();
                        break;
                    default:
                        throw new BoltIOException( Status.Request.InvalidFormat, "Bad key type: " + keyType );
                    }

                    AnyValue val = unpack();
                    if ( map.put( key, val ) != null )
                    {
                        throw new BoltIOException( Status.Request.Invalid, "Duplicate map key `" + key + "`." );
                    }
                }
            }
            return VirtualValues.map( map );
        }
    }
}




© 2015 - 2024 Weber Informatics LLC | Privacy Policy