From 0520aea6d3b3a1466edb15a2cbd390e668dd08d1 Mon Sep 17 00:00:00 2001 From: lutovich Date: Fri, 19 Jan 2018 13:57:03 +0100 Subject: [PATCH 1/7] Expose point type with Bolt V2 Introduced Bolt V1 as an extension of Bolt V1 with point type support. Added interfaces to expose point with single multi-dimensional coordinate. These interfaces are now experimental and will most likely change before the next stable release. --- .../driver/internal/InternalCoordinate.java | 68 ++++++++++ .../neo4j/driver/internal/InternalPoint.java | 86 ++++++++++++ ...tocolV1Util.java => BoltProtocolUtil.java} | 25 +++- .../async/ChannelConnectedListener.java | 4 +- .../internal/async/HandshakeHandler.java | 21 ++- .../outbound/ChunkAwareByteBufOutput.java | 10 +- .../outbound/OutboundMessageHandler.java | 4 +- .../internal/messaging/MessageFormat.java | 4 +- .../messaging/PackStreamMessageFormatV1.java | 77 ++++++----- .../messaging/PackStreamMessageFormatV2.java | 123 ++++++++++++++++++ .../internal/types/InternalTypeSystem.java | 10 +- .../internal/types/TypeConstructor.java | 9 ++ .../driver/internal/value/PointValue.java | 57 ++++++++ .../driver/internal/value/ValueAdapter.java | 9 +- .../main/java/org/neo4j/driver/v1/Value.java | 3 + .../main/java/org/neo4j/driver/v1/Values.java | 25 ++++ .../org/neo4j/driver/v1/types/Coordinate.java | 29 +++++ .../java/org/neo4j/driver/v1/types/Point.java | 31 +++++ .../org/neo4j/driver/v1/types/TypeSystem.java | 2 + ...tilTest.java => BoltProtocolUtilTest.java} | 23 ++-- .../async/ChannelConnectedListenerTest.java | 2 +- .../internal/async/HandshakeHandlerTest.java | 83 ++++++++---- .../inbound/InboundMessageHandlerTest.java | 14 +- .../internal/messaging/MessageFormatTest.java | 4 +- .../PackStreamMessageFormatV2Test.java | 46 +++++++ .../internal/util/FailingMessageFormat.java | 11 +- .../internal/util/MessageToByteBufWriter.java | 13 +- .../driver/v1/integration/PointTypeIT.java | 118 +++++++++++++++++ 28 files changed, 794 insertions(+), 117 deletions(-) create mode 100644 driver/src/main/java/org/neo4j/driver/internal/InternalCoordinate.java create mode 100644 driver/src/main/java/org/neo4j/driver/internal/InternalPoint.java rename driver/src/main/java/org/neo4j/driver/internal/async/{BoltProtocolV1Util.java => BoltProtocolUtil.java} (73%) create mode 100644 driver/src/main/java/org/neo4j/driver/internal/messaging/PackStreamMessageFormatV2.java create mode 100644 driver/src/main/java/org/neo4j/driver/internal/value/PointValue.java create mode 100644 driver/src/main/java/org/neo4j/driver/v1/types/Coordinate.java create mode 100644 driver/src/main/java/org/neo4j/driver/v1/types/Point.java rename driver/src/test/java/org/neo4j/driver/internal/async/{BoltProtocolV1UtilTest.java => BoltProtocolUtilTest.java} (68%) create mode 100644 driver/src/test/java/org/neo4j/driver/internal/messaging/PackStreamMessageFormatV2Test.java create mode 100644 driver/src/test/java/org/neo4j/driver/v1/integration/PointTypeIT.java diff --git a/driver/src/main/java/org/neo4j/driver/internal/InternalCoordinate.java b/driver/src/main/java/org/neo4j/driver/internal/InternalCoordinate.java new file mode 100644 index 0000000000..e7c7dff611 --- /dev/null +++ b/driver/src/main/java/org/neo4j/driver/internal/InternalCoordinate.java @@ -0,0 +1,68 @@ +/* + * Copyright (c) 2002-2018 "Neo Technology," + * Network Engine for Objects in Lund AB [http://neotechnology.com] + * + * This file is part of Neo4j. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.neo4j.driver.internal; + +import java.util.List; + +import org.neo4j.driver.v1.types.Coordinate; + +import static java.util.Collections.unmodifiableList; + +public class InternalCoordinate implements Coordinate +{ + private final List values; + + public InternalCoordinate( List values ) + { + this.values = unmodifiableList( values ); + } + + @Override + public List values() + { + return values; + } + + @Override + public boolean equals( Object o ) + { + if ( this == o ) + { + return true; + } + if ( o == null || getClass() != o.getClass() ) + { + return false; + } + InternalCoordinate that = (InternalCoordinate) o; + return values.equals( that.values ); + } + + @Override + public int hashCode() + { + return values.hashCode(); + } + + @Override + public String toString() + { + return String.format( "Coordinate<%s>", values ); + } +} diff --git a/driver/src/main/java/org/neo4j/driver/internal/InternalPoint.java b/driver/src/main/java/org/neo4j/driver/internal/InternalPoint.java new file mode 100644 index 0000000000..d2670e786f --- /dev/null +++ b/driver/src/main/java/org/neo4j/driver/internal/InternalPoint.java @@ -0,0 +1,86 @@ +/* + * Copyright (c) 2002-2018 "Neo Technology," + * Network Engine for Objects in Lund AB [http://neotechnology.com] + * + * This file is part of Neo4j. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.neo4j.driver.internal; + +import org.neo4j.driver.v1.types.Coordinate; +import org.neo4j.driver.v1.types.Point; + +public class InternalPoint implements Point +{ + private final long crsTableId; + private final long crsCode; + private final Coordinate coordinate; + + public InternalPoint( long crsTableId, long crsCode, Coordinate coordinate ) + { + this.crsTableId = crsTableId; + this.crsCode = crsCode; + this.coordinate = coordinate; + } + + @Override + public long crsTableId() + { + return crsTableId; + } + + @Override + public long crsCode() + { + return crsCode; + } + + @Override + public Coordinate coordinate() + { + return coordinate; + } + + @Override + public boolean equals( Object o ) + { + if ( this == o ) + { + return true; + } + if ( o == null || getClass() != o.getClass() ) + { + return false; + } + InternalPoint that = (InternalPoint) o; + return crsTableId == that.crsTableId && + crsCode == that.crsCode && + coordinate.equals( that.coordinate ); + } + + @Override + public int hashCode() + { + int result = (int) (crsTableId ^ (crsTableId >>> 32)); + result = 31 * result + (int) (crsCode ^ (crsCode >>> 32)); + result = 31 * result + coordinate.hashCode(); + return result; + } + + @Override + public String toString() + { + return String.format( "Point<%s, %s, %s>", crsTableId, crsCode, coordinate ); + } +} diff --git a/driver/src/main/java/org/neo4j/driver/internal/async/BoltProtocolV1Util.java b/driver/src/main/java/org/neo4j/driver/internal/async/BoltProtocolUtil.java similarity index 73% rename from driver/src/main/java/org/neo4j/driver/internal/async/BoltProtocolV1Util.java rename to driver/src/main/java/org/neo4j/driver/internal/async/BoltProtocolUtil.java index 63d16d59d5..709de52b0c 100644 --- a/driver/src/main/java/org/neo4j/driver/internal/async/BoltProtocolV1Util.java +++ b/driver/src/main/java/org/neo4j/driver/internal/async/BoltProtocolUtil.java @@ -22,38 +22,43 @@ import static io.netty.buffer.Unpooled.copyInt; import static io.netty.buffer.Unpooled.unreleasableBuffer; +import static java.lang.Integer.toHexString; -public final class BoltProtocolV1Util +public final class BoltProtocolUtil { public static final int HTTP = 1213486160; //== 0x48545450 == "HTTP" - public static final int BOLT_MAGIC_PREAMBLE = 0x6060B017; public static final int PROTOCOL_VERSION_1 = 1; + public static final int PROTOCOL_VERSION_2 = 2; + + public static final int BOLT_MAGIC_PREAMBLE = 0x6060B017; public static final int NO_PROTOCOL_VERSION = 0; public static final int CHUNK_HEADER_SIZE_BYTES = 2; public static final int DEFAULT_MAX_OUTBOUND_CHUNK_SIZE_BYTES = Short.MAX_VALUE / 2; - private static final ByteBuf BOLT_V1_HANDSHAKE_BUF = unreleasableBuffer( copyInt( + private static final ByteBuf HANDSHAKE_BUF = unreleasableBuffer( copyInt( BOLT_MAGIC_PREAMBLE, + PROTOCOL_VERSION_2, PROTOCOL_VERSION_1, NO_PROTOCOL_VERSION, - NO_PROTOCOL_VERSION, NO_PROTOCOL_VERSION ) ).asReadOnly(); - private BoltProtocolV1Util() + private static final String HANDSHAKE_STRING = createHandshakeString(); + + private BoltProtocolUtil() { } public static ByteBuf handshakeBuf() { - return BOLT_V1_HANDSHAKE_BUF.duplicate(); + return HANDSHAKE_BUF.duplicate(); } public static String handshakeString() { - return "[0x6060B017, 1, 0, 0, 0]"; + return HANDSHAKE_STRING; } public static void writeMessageBoundary( ByteBuf buf ) @@ -70,4 +75,10 @@ public static void writeChunkHeader( ByteBuf buf, int chunkStartIndex, int heade { buf.setShort( chunkStartIndex, headerValue ); } + + private static String createHandshakeString() + { + ByteBuf buf = handshakeBuf(); + return String.format( "[0x%s, %s, %s, %s, %s]", toHexString( buf.readInt() ), buf.readInt(), buf.readInt(), buf.readInt(), buf.readInt() ); + } } diff --git a/driver/src/main/java/org/neo4j/driver/internal/async/ChannelConnectedListener.java b/driver/src/main/java/org/neo4j/driver/internal/async/ChannelConnectedListener.java index ef3d8cb1c9..ce7d97706b 100644 --- a/driver/src/main/java/org/neo4j/driver/internal/async/ChannelConnectedListener.java +++ b/driver/src/main/java/org/neo4j/driver/internal/async/ChannelConnectedListener.java @@ -31,8 +31,8 @@ import org.neo4j.driver.v1.exceptions.ServiceUnavailableException; import static java.lang.String.format; -import static org.neo4j.driver.internal.async.BoltProtocolV1Util.handshakeBuf; -import static org.neo4j.driver.internal.async.BoltProtocolV1Util.handshakeString; +import static org.neo4j.driver.internal.async.BoltProtocolUtil.handshakeBuf; +import static org.neo4j.driver.internal.async.BoltProtocolUtil.handshakeString; public class ChannelConnectedListener implements ChannelFutureListener { diff --git a/driver/src/main/java/org/neo4j/driver/internal/async/HandshakeHandler.java b/driver/src/main/java/org/neo4j/driver/internal/async/HandshakeHandler.java index 16afecb32d..42490cb71f 100644 --- a/driver/src/main/java/org/neo4j/driver/internal/async/HandshakeHandler.java +++ b/driver/src/main/java/org/neo4j/driver/internal/async/HandshakeHandler.java @@ -31,6 +31,7 @@ import org.neo4j.driver.internal.logging.ChannelActivityLogger; import org.neo4j.driver.internal.messaging.MessageFormat; import org.neo4j.driver.internal.messaging.PackStreamMessageFormatV1; +import org.neo4j.driver.internal.messaging.PackStreamMessageFormatV2; import org.neo4j.driver.internal.util.ErrorUtil; import org.neo4j.driver.v1.Logger; import org.neo4j.driver.v1.Logging; @@ -38,9 +39,10 @@ import org.neo4j.driver.v1.exceptions.SecurityException; import org.neo4j.driver.v1.exceptions.ServiceUnavailableException; -import static org.neo4j.driver.internal.async.BoltProtocolV1Util.HTTP; -import static org.neo4j.driver.internal.async.BoltProtocolV1Util.NO_PROTOCOL_VERSION; -import static org.neo4j.driver.internal.async.BoltProtocolV1Util.PROTOCOL_VERSION_1; +import static org.neo4j.driver.internal.async.BoltProtocolUtil.HTTP; +import static org.neo4j.driver.internal.async.BoltProtocolUtil.NO_PROTOCOL_VERSION; +import static org.neo4j.driver.internal.async.BoltProtocolUtil.PROTOCOL_VERSION_1; +import static org.neo4j.driver.internal.async.BoltProtocolUtil.PROTOCOL_VERSION_2; public class HandshakeHandler extends ReplayingDecoder { @@ -121,9 +123,10 @@ protected void decode( ChannelHandlerContext ctx, ByteBuf in, List out ) switch ( serverSuggestedVersion ) { case PROTOCOL_VERSION_1: - MessageFormat messageFormat = new PackStreamMessageFormatV1(); - pipelineBuilder.build( messageFormat, pipeline, logging ); - handshakeCompletedPromise.setSuccess(); + protocolSelected( new PackStreamMessageFormatV1(), pipeline ); + break; + case PROTOCOL_VERSION_2: + protocolSelected( new PackStreamMessageFormatV2(), pipeline ); break; case NO_PROTOCOL_VERSION: fail( ctx, protocolNoSupportedByServerError() ); @@ -137,6 +140,12 @@ protected void decode( ChannelHandlerContext ctx, ByteBuf in, List out ) } } + private void protocolSelected( MessageFormat messageFormat, ChannelPipeline pipeline ) + { + pipelineBuilder.build( messageFormat, pipeline, logging ); + handshakeCompletedPromise.setSuccess(); + } + private void fail( ChannelHandlerContext ctx, Throwable error ) { ctx.close().addListener( future -> handshakeCompletedPromise.tryFailure( error ) ); diff --git a/driver/src/main/java/org/neo4j/driver/internal/async/outbound/ChunkAwareByteBufOutput.java b/driver/src/main/java/org/neo4j/driver/internal/async/outbound/ChunkAwareByteBufOutput.java index 319f8d0b8c..5ca5321525 100644 --- a/driver/src/main/java/org/neo4j/driver/internal/async/outbound/ChunkAwareByteBufOutput.java +++ b/driver/src/main/java/org/neo4j/driver/internal/async/outbound/ChunkAwareByteBufOutput.java @@ -20,12 +20,12 @@ import io.netty.buffer.ByteBuf; -import org.neo4j.driver.internal.async.BoltProtocolV1Util; +import org.neo4j.driver.internal.async.BoltProtocolUtil; import org.neo4j.driver.internal.packstream.PackOutput; import static java.util.Objects.requireNonNull; -import static org.neo4j.driver.internal.async.BoltProtocolV1Util.CHUNK_HEADER_SIZE_BYTES; -import static org.neo4j.driver.internal.async.BoltProtocolV1Util.DEFAULT_MAX_OUTBOUND_CHUNK_SIZE_BYTES; +import static org.neo4j.driver.internal.async.BoltProtocolUtil.CHUNK_HEADER_SIZE_BYTES; +import static org.neo4j.driver.internal.async.BoltProtocolUtil.DEFAULT_MAX_OUTBOUND_CHUNK_SIZE_BYTES; public class ChunkAwareByteBufOutput implements PackOutput { @@ -138,7 +138,7 @@ private void ensureCanFitInCurrentChunk( int numberOfBytes ) private void startNewChunk( int index ) { currentChunkStartIndex = index; - BoltProtocolV1Util.writeEmptyChunkHeader( buf ); + BoltProtocolUtil.writeEmptyChunkHeader( buf ); currentChunkSize = CHUNK_HEADER_SIZE_BYTES; } @@ -146,7 +146,7 @@ private void writeChunkSizeHeader() { // go to the beginning of the chunk and write the size header int chunkBodySize = currentChunkSize - CHUNK_HEADER_SIZE_BYTES; - BoltProtocolV1Util.writeChunkHeader( buf, currentChunkStartIndex, chunkBodySize ); + BoltProtocolUtil.writeChunkHeader( buf, currentChunkStartIndex, chunkBodySize ); } private int availableBytesInCurrentChunk() diff --git a/driver/src/main/java/org/neo4j/driver/internal/async/outbound/OutboundMessageHandler.java b/driver/src/main/java/org/neo4j/driver/internal/async/outbound/OutboundMessageHandler.java index 67a0970864..bd5f89f12a 100644 --- a/driver/src/main/java/org/neo4j/driver/internal/async/outbound/OutboundMessageHandler.java +++ b/driver/src/main/java/org/neo4j/driver/internal/async/outbound/OutboundMessageHandler.java @@ -25,7 +25,7 @@ import java.util.List; -import org.neo4j.driver.internal.async.BoltProtocolV1Util; +import org.neo4j.driver.internal.async.BoltProtocolUtil; import org.neo4j.driver.internal.logging.ChannelActivityLogger; import org.neo4j.driver.internal.messaging.Message; import org.neo4j.driver.internal.messaging.MessageFormat; @@ -95,7 +95,7 @@ protected void encode( ChannelHandlerContext ctx, Message msg, List out log.trace( "C: %s", hexDump( messageBuf ) ); } - BoltProtocolV1Util.writeMessageBoundary( messageBuf ); + BoltProtocolUtil.writeMessageBoundary( messageBuf ); out.add( messageBuf ); } diff --git a/driver/src/main/java/org/neo4j/driver/internal/messaging/MessageFormat.java b/driver/src/main/java/org/neo4j/driver/internal/messaging/MessageFormat.java index 1615be1946..0d09dd2a24 100644 --- a/driver/src/main/java/org/neo4j/driver/internal/messaging/MessageFormat.java +++ b/driver/src/main/java/org/neo4j/driver/internal/messaging/MessageFormat.java @@ -27,7 +27,7 @@ public interface MessageFormat { interface Writer { - Writer write( Message msg ) throws IOException; + void write( Message msg ) throws IOException; } interface Reader @@ -38,6 +38,4 @@ interface Reader Writer newWriter( PackOutput output, boolean byteArraySupportEnabled ); Reader newReader( PackInput input ); - - int version(); } diff --git a/driver/src/main/java/org/neo4j/driver/internal/messaging/PackStreamMessageFormatV1.java b/driver/src/main/java/org/neo4j/driver/internal/messaging/PackStreamMessageFormatV1.java index 96ee105741..95a035611c 100644 --- a/driver/src/main/java/org/neo4j/driver/internal/messaging/PackStreamMessageFormatV1.java +++ b/driver/src/main/java/org/neo4j/driver/internal/messaging/PackStreamMessageFormatV1.java @@ -68,37 +68,29 @@ public class PackStreamMessageFormatV1 implements MessageFormat public static final byte UNBOUND_RELATIONSHIP = 'r'; public static final byte PATH = 'P'; - public static final int VERSION = 1; - public static final int NODE_FIELDS = 3; @Override public MessageFormat.Writer newWriter( PackOutput output, boolean byteArraySupportEnabled ) { - return new Writer( output, byteArraySupportEnabled ); + return new WriterV1( output, byteArraySupportEnabled ); } @Override public MessageFormat.Reader newReader( PackInput input ) { - return new Reader( input ); - } - - @Override - public int version() - { - return VERSION; + return new ReaderV1( input ); } - public static class Writer implements MessageFormat.Writer, MessageHandler + static class WriterV1 implements MessageFormat.Writer, MessageHandler { - private final PackStream.Packer packer; + final PackStream.Packer packer; /** * @param output interface to write messages to * @param byteArraySupportEnabled specify if support to pack/write byte array to server */ - public Writer( PackOutput output, boolean byteArraySupportEnabled ) + WriterV1( PackOutput output, boolean byteArraySupportEnabled ) { if( byteArraySupportEnabled ) { @@ -204,7 +196,19 @@ private void packRawMap( Map map ) throws IOException private void packValue( Value value ) throws IOException { - switch ( ( (InternalValue) value ).typeConstructor() ) + if ( value instanceof InternalValue ) + { + packInternalValue( ((InternalValue) value) ); + } + else + { + throw new IllegalArgumentException( "Unable to pack: " + value ); + } + } + + void packInternalValue( InternalValue value ) throws IOException + { + switch ( value.typeConstructor() ) { case NULL_TyCon: packer.packNull(); @@ -324,10 +328,9 @@ private void packValue( Value value ) throws IOException } @Override - public Writer write( Message msg ) throws IOException + public void write( Message msg ) throws IOException { msg.dispatch( this ); - return this; } private void packNode( Node node ) throws IOException @@ -357,11 +360,11 @@ private void packProperties( Entity entity ) throws IOException } } - public static class Reader implements MessageFormat.Reader + static class ReaderV1 implements MessageFormat.Reader { - private final PackStream.Unpacker unpacker; + final PackStream.Unpacker unpacker; - public Reader( PackInput input ) + ReaderV1( PackInput input ) { unpacker = new PackStream.Unpacker( input ); } @@ -499,24 +502,32 @@ private Value unpackValue() throws IOException case STRUCT: { long size = unpacker.unpackStructHeader(); - switch ( unpacker.unpackStructSignature() ) - { - case NODE: - ensureCorrectStructSize( "NODE", NODE_FIELDS, size ); - InternalNode adapted = unpackNode(); - return new NodeValue( adapted ); - case RELATIONSHIP: - ensureCorrectStructSize( "RELATIONSHIP", 5, size ); - return unpackRelationship(); - case PATH: - ensureCorrectStructSize( "PATH", 3, size ); - return unpackPath(); - } + byte structType = unpacker.unpackStructSignature(); + return unpackStruct( size, structType ); } } throw new IOException( "Unknown value type: " + type ); } + Value unpackStruct( long size, byte type ) throws IOException + { + switch ( type ) + { + case NODE: + ensureCorrectStructSize( "NODE", NODE_FIELDS, size ); + InternalNode adapted = unpackNode(); + return new NodeValue( adapted ); + case RELATIONSHIP: + ensureCorrectStructSize( "RELATIONSHIP", 5, size ); + return unpackRelationship(); + case PATH: + ensureCorrectStructSize( "PATH", 3, size ); + return unpackPath(); + default: + throw new IOException( "Unknown struct type: " + type ); + } + } + private Value unpackRelationship() throws IOException { long urn = unpacker.unpackLong(); @@ -608,7 +619,7 @@ private Value unpackPath() throws IOException return new PathValue( new InternalPath( Arrays.asList( segments ), Arrays.asList( nodes ), Arrays.asList( rels ) ) ); } - private void ensureCorrectStructSize( String structName, int expected, long actual ) + void ensureCorrectStructSize( String structName, int expected, long actual ) { if ( expected != actual ) { diff --git a/driver/src/main/java/org/neo4j/driver/internal/messaging/PackStreamMessageFormatV2.java b/driver/src/main/java/org/neo4j/driver/internal/messaging/PackStreamMessageFormatV2.java new file mode 100644 index 0000000000..53644b85d1 --- /dev/null +++ b/driver/src/main/java/org/neo4j/driver/internal/messaging/PackStreamMessageFormatV2.java @@ -0,0 +1,123 @@ +/* + * Copyright (c) 2002-2018 "Neo Technology," + * Network Engine for Objects in Lund AB [http://neotechnology.com] + * + * This file is part of Neo4j. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.neo4j.driver.internal.messaging; + +import java.io.IOException; +import java.util.ArrayList; +import java.util.List; + +import org.neo4j.driver.internal.InternalCoordinate; +import org.neo4j.driver.internal.InternalPoint; +import org.neo4j.driver.internal.packstream.PackInput; +import org.neo4j.driver.internal.packstream.PackOutput; +import org.neo4j.driver.internal.value.InternalValue; +import org.neo4j.driver.internal.value.PointValue; +import org.neo4j.driver.v1.Value; +import org.neo4j.driver.v1.types.Point; + +import static org.neo4j.driver.internal.types.TypeConstructor.POINT_TyCon; + +public class PackStreamMessageFormatV2 extends PackStreamMessageFormatV1 +{ + private static final byte POINT_STRUCT_TYPE = 'X'; + private static final int POINT_STRUCT_SIZE = 3; + + @Override + public MessageFormat.Writer newWriter( PackOutput output, boolean byteArraySupportEnabled ) + { + if ( !byteArraySupportEnabled ) + { + throw new IllegalArgumentException( "Bolt V2 should support byte arrays" ); + } + return new WriterV2( output ); + } + + @Override + public MessageFormat.Reader newReader( PackInput input ) + { + return new ReaderV2( input ); + } + + private static class WriterV2 extends WriterV1 + { + WriterV2( PackOutput output ) + { + super( output, true ); + } + + @Override + void packInternalValue( InternalValue value ) throws IOException + { + if ( value.typeConstructor() == POINT_TyCon ) + { + packPoint( value.asPoint() ); + } + else + { + super.packInternalValue( value ); + } + } + + private void packPoint( Point point ) throws IOException + { + packer.packStructHeader( POINT_STRUCT_SIZE, POINT_STRUCT_TYPE ); + packer.pack( point.crsTableId() ); + packer.pack( point.crsCode() ); + packer.pack( point.coordinate().values() ); + } + } + + private static class ReaderV2 extends ReaderV1 + { + ReaderV2( PackInput input ) + { + super( input ); + } + + @Override + Value unpackStruct( long size, byte type ) throws IOException + { + if ( type == POINT_STRUCT_TYPE ) + { + ensureCorrectStructSize( POINT_TyCon.typeName(), POINT_STRUCT_SIZE, size ); + return unpackPoint(); + } + else + { + return super.unpackStruct( size, type ); + } + } + + private Value unpackPoint() throws IOException + { + long crsTableId = unpacker.unpackLong(); + long crsCode = unpacker.unpackLong(); + + int coordinateSize = (int) unpacker.unpackListHeader(); + List coordinate = new ArrayList<>( coordinateSize ); + for ( int i = 0; i < coordinateSize; i++ ) + { + coordinate.add( unpacker.unpackDouble() ); + } + + Point point = new InternalPoint( crsTableId, crsCode, new InternalCoordinate( coordinate ) ); + return new PointValue( point ); + } + } +} diff --git a/driver/src/main/java/org/neo4j/driver/internal/types/InternalTypeSystem.java b/driver/src/main/java/org/neo4j/driver/internal/types/InternalTypeSystem.java index 5367ecf478..7cceacfd34 100644 --- a/driver/src/main/java/org/neo4j/driver/internal/types/InternalTypeSystem.java +++ b/driver/src/main/java/org/neo4j/driver/internal/types/InternalTypeSystem.java @@ -24,6 +24,7 @@ import static org.neo4j.driver.internal.types.TypeConstructor.ANY_TyCon; import static org.neo4j.driver.internal.types.TypeConstructor.BOOLEAN_TyCon; +import static org.neo4j.driver.internal.types.TypeConstructor.BYTES_TyCon; import static org.neo4j.driver.internal.types.TypeConstructor.FLOAT_TyCon; import static org.neo4j.driver.internal.types.TypeConstructor.INTEGER_TyCon; import static org.neo4j.driver.internal.types.TypeConstructor.LIST_TyCon; @@ -32,8 +33,8 @@ import static org.neo4j.driver.internal.types.TypeConstructor.NULL_TyCon; import static org.neo4j.driver.internal.types.TypeConstructor.NUMBER_TyCon; import static org.neo4j.driver.internal.types.TypeConstructor.PATH_TyCon; +import static org.neo4j.driver.internal.types.TypeConstructor.POINT_TyCon; import static org.neo4j.driver.internal.types.TypeConstructor.RELATIONSHIP_TyCon; -import static org.neo4j.driver.internal.types.TypeConstructor.BYTES_TyCon; import static org.neo4j.driver.internal.types.TypeConstructor.STRING_TyCon; /** @@ -58,6 +59,7 @@ public class InternalTypeSystem implements TypeSystem private final TypeRepresentation nodeType = constructType( NODE_TyCon ); private final TypeRepresentation relationshipType = constructType( RELATIONSHIP_TyCon ); private final TypeRepresentation pathType = constructType( PATH_TyCon ); + private final TypeRepresentation pointType = constructType( POINT_TyCon ); private final TypeRepresentation nullType = constructType( NULL_TyCon ); private InternalTypeSystem() @@ -148,6 +150,12 @@ public Type PATH() return pathType; } + @Override + public Type POINT() + { + return pointType; + } + /** the Cypher type NULL */ @Override public Type NULL() diff --git a/driver/src/main/java/org/neo4j/driver/internal/types/TypeConstructor.java b/driver/src/main/java/org/neo4j/driver/internal/types/TypeConstructor.java index a20eab7c72..51d0914587 100644 --- a/driver/src/main/java/org/neo4j/driver/internal/types/TypeConstructor.java +++ b/driver/src/main/java/org/neo4j/driver/internal/types/TypeConstructor.java @@ -139,6 +139,15 @@ public String typeName() } }, + POINT_TyCon + { + @Override + public String typeName() + { + return "POINT"; + } + }, + NULL_TyCon { @Override public String typeName() diff --git a/driver/src/main/java/org/neo4j/driver/internal/value/PointValue.java b/driver/src/main/java/org/neo4j/driver/internal/value/PointValue.java new file mode 100644 index 0000000000..71828c2d86 --- /dev/null +++ b/driver/src/main/java/org/neo4j/driver/internal/value/PointValue.java @@ -0,0 +1,57 @@ +/* + * Copyright (c) 2002-2018 "Neo Technology," + * Network Engine for Objects in Lund AB [http://neotechnology.com] + * + * This file is part of Neo4j. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.neo4j.driver.internal.value; + +import org.neo4j.driver.internal.types.InternalTypeSystem; +import org.neo4j.driver.v1.types.Point; +import org.neo4j.driver.v1.types.Type; + +public class PointValue extends ValueAdapter +{ + private final Point point; + + public PointValue( Point point ) + { + this.point = point; + } + + @Override + public Point asPoint() + { + return point; + } + + @Override + public Object asObject() + { + return point; + } + + @Override + public String toString( Format valueFormat ) + { + return maybeWithType( valueFormat.includeType(), point.toString() ); + } + + @Override + public Type type() + { + return InternalTypeSystem.TYPE_SYSTEM.POINT(); + } +} diff --git a/driver/src/main/java/org/neo4j/driver/internal/value/ValueAdapter.java b/driver/src/main/java/org/neo4j/driver/internal/value/ValueAdapter.java index 85538e34e6..cd5fd2a661 100644 --- a/driver/src/main/java/org/neo4j/driver/internal/value/ValueAdapter.java +++ b/driver/src/main/java/org/neo4j/driver/internal/value/ValueAdapter.java @@ -31,6 +31,7 @@ import org.neo4j.driver.v1.types.Entity; import org.neo4j.driver.v1.types.Node; import org.neo4j.driver.v1.types.Path; +import org.neo4j.driver.v1.types.Point; import org.neo4j.driver.v1.types.Relationship; import org.neo4j.driver.v1.types.Type; import org.neo4j.driver.v1.util.Function; @@ -38,8 +39,8 @@ import static java.lang.String.format; import static java.util.Collections.emptyList; import static org.neo4j.driver.internal.value.InternalValue.Format.VALUE_ONLY; -import static org.neo4j.driver.v1.Values.ofValue; import static org.neo4j.driver.v1.Values.ofObject; +import static org.neo4j.driver.v1.Values.ofValue; public abstract class ValueAdapter extends InternalMapAccessorWithDefaultValue implements InternalValue { @@ -186,6 +187,12 @@ public Relationship asRelationship() throw new Uncoercible( type().name(), "Relationship" ); } + @Override + public Point asPoint() + { + throw new Uncoercible( type().name(), "Point" ); + } + @Override public Value get( int index ) { diff --git a/driver/src/main/java/org/neo4j/driver/v1/Value.java b/driver/src/main/java/org/neo4j/driver/v1/Value.java index 2e3687e038..448bdbb92b 100644 --- a/driver/src/main/java/org/neo4j/driver/v1/Value.java +++ b/driver/src/main/java/org/neo4j/driver/v1/Value.java @@ -29,6 +29,7 @@ import org.neo4j.driver.v1.types.MapAccessorWithDefaultValue; import org.neo4j.driver.v1.types.Node; import org.neo4j.driver.v1.types.Path; +import org.neo4j.driver.v1.types.Point; import org.neo4j.driver.v1.types.Relationship; import org.neo4j.driver.v1.types.Type; import org.neo4j.driver.v1.types.TypeSystem; @@ -289,6 +290,8 @@ public interface Value extends MapAccessor, MapAccessorWithDefaultValue */ Path asPath(); + Point asPoint(); + // Force implementation @Override boolean equals( Object other ); diff --git a/driver/src/main/java/org/neo4j/driver/v1/Values.java b/driver/src/main/java/org/neo4j/driver/v1/Values.java index 0c1a03fb86..678380fb23 100644 --- a/driver/src/main/java/org/neo4j/driver/v1/Values.java +++ b/driver/src/main/java/org/neo4j/driver/v1/Values.java @@ -26,8 +26,11 @@ import java.util.Iterator; import java.util.List; import java.util.Map; +import java.util.stream.DoubleStream; import org.neo4j.driver.internal.AsValue; +import org.neo4j.driver.internal.InternalCoordinate; +import org.neo4j.driver.internal.InternalPoint; import org.neo4j.driver.internal.value.BooleanValue; import org.neo4j.driver.internal.value.BytesValue; import org.neo4j.driver.internal.value.FloatValue; @@ -35,15 +38,18 @@ import org.neo4j.driver.internal.value.ListValue; import org.neo4j.driver.internal.value.MapValue; import org.neo4j.driver.internal.value.NullValue; +import org.neo4j.driver.internal.value.PointValue; import org.neo4j.driver.internal.value.StringValue; import org.neo4j.driver.v1.exceptions.ClientException; import org.neo4j.driver.v1.types.Entity; import org.neo4j.driver.v1.types.Node; import org.neo4j.driver.v1.types.Path; +import org.neo4j.driver.v1.types.Point; import org.neo4j.driver.v1.types.Relationship; import org.neo4j.driver.v1.types.TypeSystem; import org.neo4j.driver.v1.util.Function; +import static java.util.stream.Collectors.toList; import static org.neo4j.driver.internal.util.Iterables.newHashMapWithSize; /** @@ -257,6 +263,13 @@ public static Value value( final Map val ) return new MapValue( asValues ); } + public static Value point( long crsTableId, long crsCode, double... coordinates ) + { + InternalCoordinate coordinate = new InternalCoordinate( DoubleStream.of( coordinates ).boxed().collect( toList() ) ); + InternalPoint point = new InternalPoint( crsTableId, crsCode, coordinate ); + return new PointValue( point ); + } + /** * Helper function for creating a map of parameters, this can be used when you {@link * StatementRunner#run(String, Value) run} statements. @@ -472,6 +485,11 @@ public static Function ofPath() return PATH; } + public static Function ofPoint() + { + return POINT; + } + /** * Converts values to {@link List} of {@link Object}. * @return a function that returns {@link Value#asList()} of a {@link Value} @@ -619,6 +637,13 @@ public Path apply( Value val ) return val.asPath(); } }; + private static final Function POINT = new Function() + { + public Point apply( Value val ) + { + return val.asPoint(); + } + }; private static void assertParameter( Object value ) { diff --git a/driver/src/main/java/org/neo4j/driver/v1/types/Coordinate.java b/driver/src/main/java/org/neo4j/driver/v1/types/Coordinate.java new file mode 100644 index 0000000000..b5d3a1fe38 --- /dev/null +++ b/driver/src/main/java/org/neo4j/driver/v1/types/Coordinate.java @@ -0,0 +1,29 @@ +/* + * Copyright (c) 2002-2018 "Neo Technology," + * Network Engine for Objects in Lund AB [http://neotechnology.com] + * + * This file is part of Neo4j. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.neo4j.driver.v1.types; + +import java.util.List; + +import org.neo4j.driver.v1.util.Experimental; + +@Experimental +public interface Coordinate +{ + List values(); +} diff --git a/driver/src/main/java/org/neo4j/driver/v1/types/Point.java b/driver/src/main/java/org/neo4j/driver/v1/types/Point.java new file mode 100644 index 0000000000..26d3f99702 --- /dev/null +++ b/driver/src/main/java/org/neo4j/driver/v1/types/Point.java @@ -0,0 +1,31 @@ +/* + * Copyright (c) 2002-2018 "Neo Technology," + * Network Engine for Objects in Lund AB [http://neotechnology.com] + * + * This file is part of Neo4j. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.neo4j.driver.v1.types; + +import org.neo4j.driver.v1.util.Experimental; + +@Experimental +public interface Point +{ + long crsTableId(); + + long crsCode(); + + Coordinate coordinate(); +} diff --git a/driver/src/main/java/org/neo4j/driver/v1/types/TypeSystem.java b/driver/src/main/java/org/neo4j/driver/v1/types/TypeSystem.java index 59350b62df..01c0aafeb2 100644 --- a/driver/src/main/java/org/neo4j/driver/v1/types/TypeSystem.java +++ b/driver/src/main/java/org/neo4j/driver/v1/types/TypeSystem.java @@ -53,5 +53,7 @@ public interface TypeSystem Type PATH(); + Type POINT(); + Type NULL(); } diff --git a/driver/src/test/java/org/neo4j/driver/internal/async/BoltProtocolV1UtilTest.java b/driver/src/test/java/org/neo4j/driver/internal/async/BoltProtocolUtilTest.java similarity index 68% rename from driver/src/test/java/org/neo4j/driver/internal/async/BoltProtocolV1UtilTest.java rename to driver/src/test/java/org/neo4j/driver/internal/async/BoltProtocolUtilTest.java index 94c469eca8..83d389b774 100644 --- a/driver/src/test/java/org/neo4j/driver/internal/async/BoltProtocolV1UtilTest.java +++ b/driver/src/test/java/org/neo4j/driver/internal/async/BoltProtocolUtilTest.java @@ -23,31 +23,32 @@ import org.junit.Test; import static org.junit.Assert.assertEquals; -import static org.neo4j.driver.internal.async.BoltProtocolV1Util.BOLT_MAGIC_PREAMBLE; -import static org.neo4j.driver.internal.async.BoltProtocolV1Util.NO_PROTOCOL_VERSION; -import static org.neo4j.driver.internal.async.BoltProtocolV1Util.PROTOCOL_VERSION_1; -import static org.neo4j.driver.internal.async.BoltProtocolV1Util.handshakeBuf; -import static org.neo4j.driver.internal.async.BoltProtocolV1Util.handshakeString; -import static org.neo4j.driver.internal.async.BoltProtocolV1Util.writeChunkHeader; -import static org.neo4j.driver.internal.async.BoltProtocolV1Util.writeEmptyChunkHeader; -import static org.neo4j.driver.internal.async.BoltProtocolV1Util.writeMessageBoundary; +import static org.neo4j.driver.internal.async.BoltProtocolUtil.BOLT_MAGIC_PREAMBLE; +import static org.neo4j.driver.internal.async.BoltProtocolUtil.NO_PROTOCOL_VERSION; +import static org.neo4j.driver.internal.async.BoltProtocolUtil.PROTOCOL_VERSION_1; +import static org.neo4j.driver.internal.async.BoltProtocolUtil.PROTOCOL_VERSION_2; +import static org.neo4j.driver.internal.async.BoltProtocolUtil.handshakeBuf; +import static org.neo4j.driver.internal.async.BoltProtocolUtil.handshakeString; +import static org.neo4j.driver.internal.async.BoltProtocolUtil.writeChunkHeader; +import static org.neo4j.driver.internal.async.BoltProtocolUtil.writeEmptyChunkHeader; +import static org.neo4j.driver.internal.async.BoltProtocolUtil.writeMessageBoundary; import static org.neo4j.driver.v1.util.TestUtil.assertByteBufContains; -public class BoltProtocolV1UtilTest +public class BoltProtocolUtilTest { @Test public void shouldReturnHandshakeBuf() { assertByteBufContains( handshakeBuf(), - BOLT_MAGIC_PREAMBLE, PROTOCOL_VERSION_1, NO_PROTOCOL_VERSION, NO_PROTOCOL_VERSION, NO_PROTOCOL_VERSION + BOLT_MAGIC_PREAMBLE, PROTOCOL_VERSION_2, PROTOCOL_VERSION_1, NO_PROTOCOL_VERSION, NO_PROTOCOL_VERSION ); } @Test public void shouldReturnHandshakeString() { - assertEquals( "[0x6060B017, 1, 0, 0, 0]", handshakeString() ); + assertEquals( "[0x6060b017, 2, 1, 0, 0]", handshakeString() ); } @Test diff --git a/driver/src/test/java/org/neo4j/driver/internal/async/ChannelConnectedListenerTest.java b/driver/src/test/java/org/neo4j/driver/internal/async/ChannelConnectedListenerTest.java index 873b8acb44..bca574b652 100644 --- a/driver/src/test/java/org/neo4j/driver/internal/async/ChannelConnectedListenerTest.java +++ b/driver/src/test/java/org/neo4j/driver/internal/async/ChannelConnectedListenerTest.java @@ -34,7 +34,7 @@ import static org.junit.Assert.assertTrue; import static org.junit.Assert.fail; import static org.neo4j.driver.internal.BoltServerAddress.LOCAL_DEFAULT; -import static org.neo4j.driver.internal.async.BoltProtocolV1Util.handshakeBuf; +import static org.neo4j.driver.internal.async.BoltProtocolUtil.handshakeBuf; import static org.neo4j.driver.internal.logging.DevNullLogging.DEV_NULL_LOGGING; import static org.neo4j.driver.v1.util.TestUtil.await; diff --git a/driver/src/test/java/org/neo4j/driver/internal/async/HandshakeHandlerTest.java b/driver/src/test/java/org/neo4j/driver/internal/async/HandshakeHandlerTest.java index b572f869c0..7739884fc7 100644 --- a/driver/src/test/java/org/neo4j/driver/internal/async/HandshakeHandlerTest.java +++ b/driver/src/test/java/org/neo4j/driver/internal/async/HandshakeHandlerTest.java @@ -18,6 +18,7 @@ */ package org.neo4j.driver.internal.async; +import io.netty.channel.ChannelPipeline; import io.netty.channel.ChannelPromise; import io.netty.channel.embedded.EmbeddedChannel; import io.netty.handler.codec.DecoderException; @@ -33,7 +34,11 @@ import org.neo4j.driver.internal.async.inbound.InboundMessageHandler; import org.neo4j.driver.internal.async.inbound.MessageDecoder; import org.neo4j.driver.internal.async.outbound.OutboundMessageHandler; +import org.neo4j.driver.internal.messaging.MessageFormat; +import org.neo4j.driver.internal.messaging.PackStreamMessageFormatV1; +import org.neo4j.driver.internal.messaging.PackStreamMessageFormatV2; import org.neo4j.driver.internal.util.ErrorUtil; +import org.neo4j.driver.v1.Logging; import org.neo4j.driver.v1.exceptions.ClientException; import org.neo4j.driver.v1.exceptions.SecurityException; import org.neo4j.driver.v1.exceptions.ServiceUnavailableException; @@ -46,10 +51,11 @@ import static org.junit.Assert.assertNull; import static org.junit.Assert.assertThat; import static org.junit.Assert.fail; +import static org.neo4j.driver.internal.async.BoltProtocolUtil.HTTP; +import static org.neo4j.driver.internal.async.BoltProtocolUtil.NO_PROTOCOL_VERSION; +import static org.neo4j.driver.internal.async.BoltProtocolUtil.PROTOCOL_VERSION_1; +import static org.neo4j.driver.internal.async.BoltProtocolUtil.PROTOCOL_VERSION_2; import static org.neo4j.driver.internal.async.ChannelAttributes.setMessageDispatcher; -import static org.neo4j.driver.internal.async.BoltProtocolV1Util.HTTP; -import static org.neo4j.driver.internal.async.BoltProtocolV1Util.NO_PROTOCOL_VERSION; -import static org.neo4j.driver.internal.async.BoltProtocolV1Util.PROTOCOL_VERSION_1; import static org.neo4j.driver.internal.logging.DevNullLogging.DEV_NULL_LOGGING; import static org.neo4j.driver.v1.util.TestUtil.await; @@ -184,25 +190,13 @@ public void shouldTranslateSSLHandshakeException() @Test public void shouldSelectProtocolV1WhenServerSuggests() { - ChannelPromise handshakeCompletedPromise = channel.newPromise(); - HandshakeHandler handler = newHandler( handshakeCompletedPromise ); - channel.pipeline().addLast( handler ); - - channel.pipeline().fireChannelRead( copyInt( PROTOCOL_VERSION_1 ) ); - - // handshake handler itself should be removed - assertNull( channel.pipeline().get( HandshakeHandler.class ) ); - - // all inbound handlers should be set - assertNotNull( channel.pipeline().get( ChunkDecoder.class ) ); - assertNotNull( channel.pipeline().get( MessageDecoder.class ) ); - assertNotNull( channel.pipeline().get( InboundMessageHandler.class ) ); - - // all outbound handlers should be set - assertNotNull( channel.pipeline().get( OutboundMessageHandler.class ) ); + testProtocolSelection( PROTOCOL_VERSION_1, PackStreamMessageFormatV1.class ); + } - // promise should be successful - assertNull( await( handshakeCompletedPromise ) ); + @Test + public void shouldSelectProtocolV2WhenServerSuggests() + { + testProtocolSelection( PROTOCOL_VERSION_2, PackStreamMessageFormatV2.class ); } @Test @@ -274,9 +268,52 @@ private void testFailure( int serverSuggestedVersion, String expectedMessagePref assertNull( await( channel.closeFuture() ) ); } + private void testProtocolSelection( int protocolVersion, Class expectedMessageFormatClass ) + { + ChannelPromise handshakeCompletedPromise = channel.newPromise(); + MemorizingChannelPipelineBuilder pipelineBuilder = new MemorizingChannelPipelineBuilder(); + HandshakeHandler handler = newHandler( pipelineBuilder, handshakeCompletedPromise ); + channel.pipeline().addLast( handler ); + + channel.pipeline().fireChannelRead( copyInt( protocolVersion ) ); + + // expected message format should've been used + assertThat( pipelineBuilder.usedMessageFormat, instanceOf( expectedMessageFormatClass ) ); + + // handshake handler itself should be removed + assertNull( channel.pipeline().get( HandshakeHandler.class ) ); + + // all inbound handlers should be set + assertNotNull( channel.pipeline().get( ChunkDecoder.class ) ); + assertNotNull( channel.pipeline().get( MessageDecoder.class ) ); + assertNotNull( channel.pipeline().get( InboundMessageHandler.class ) ); + + // all outbound handlers should be set + assertNotNull( channel.pipeline().get( OutboundMessageHandler.class ) ); + + // promise should be successful + assertNull( await( handshakeCompletedPromise ) ); + } + private static HandshakeHandler newHandler( ChannelPromise handshakeCompletedPromise ) { - return new HandshakeHandler( new ChannelPipelineBuilderImpl(), handshakeCompletedPromise, - DEV_NULL_LOGGING ); + return newHandler( new ChannelPipelineBuilderImpl(), handshakeCompletedPromise ); + } + + private static HandshakeHandler newHandler( ChannelPipelineBuilder pipelineBuilder, ChannelPromise handshakeCompletedPromise ) + { + return new HandshakeHandler( pipelineBuilder, handshakeCompletedPromise, DEV_NULL_LOGGING ); + } + + private static class MemorizingChannelPipelineBuilder extends ChannelPipelineBuilderImpl + { + MessageFormat usedMessageFormat; + + @Override + public void build( MessageFormat messageFormat, ChannelPipeline pipeline, Logging logging ) + { + usedMessageFormat = messageFormat; + super.build( messageFormat, pipeline, logging ); + } } } diff --git a/driver/src/test/java/org/neo4j/driver/internal/async/inbound/InboundMessageHandlerTest.java b/driver/src/test/java/org/neo4j/driver/internal/async/inbound/InboundMessageHandlerTest.java index e753fc1125..6708a7d8af 100644 --- a/driver/src/test/java/org/neo4j/driver/internal/async/inbound/InboundMessageHandlerTest.java +++ b/driver/src/test/java/org/neo4j/driver/internal/async/inbound/InboundMessageHandlerTest.java @@ -38,6 +38,7 @@ import org.neo4j.driver.internal.messaging.RecordMessage; import org.neo4j.driver.internal.messaging.SuccessMessage; import org.neo4j.driver.internal.spi.ResponseHandler; +import org.neo4j.driver.internal.util.MessageToByteBufWriter; import org.neo4j.driver.v1.Value; import org.neo4j.driver.v1.exceptions.Neo4jException; @@ -52,19 +53,20 @@ import static org.mockito.Mockito.when; import static org.neo4j.driver.internal.logging.DevNullLogging.DEV_NULL_LOGGING; import static org.neo4j.driver.internal.messaging.ResetMessage.RESET; -import static org.neo4j.driver.internal.util.MessageToByteBufWriter.asByteBuf; import static org.neo4j.driver.v1.Values.value; public class InboundMessageHandlerTest { private EmbeddedChannel channel; private InboundMessageDispatcher messageDispatcher; + private MessageToByteBufWriter writer; @Before public void setUp() { channel = new EmbeddedChannel(); messageDispatcher = new InboundMessageDispatcher( channel, DEV_NULL_LOGGING ); + writer = new MessageToByteBufWriter( new PackStreamMessageFormatV1() ); ChannelAttributes.setMessageDispatcher( channel, messageDispatcher ); InboundMessageHandler handler = new InboundMessageHandler( new PackStreamMessageFormatV1(), DEV_NULL_LOGGING ); @@ -89,7 +91,7 @@ public void shouldReadSuccessMessage() Map metadata = new HashMap<>(); metadata.put( "key1", value( 1 ) ); metadata.put( "key2", value( 2 ) ); - channel.writeInbound( asByteBuf( new SuccessMessage( metadata ) ) ); + channel.writeInbound( writer.asByteBuf( new SuccessMessage( metadata ) ) ); verify( responseHandler ).onSuccess( metadata ); } @@ -100,7 +102,7 @@ public void shouldReadFailureMessage() ResponseHandler responseHandler = mock( ResponseHandler.class ); messageDispatcher.queue( responseHandler ); - channel.writeInbound( asByteBuf( new FailureMessage( "Neo.TransientError.General.ReadOnly", "Hi!" ) ) ); + channel.writeInbound( writer.asByteBuf( new FailureMessage( "Neo.TransientError.General.ReadOnly", "Hi!" ) ) ); ArgumentCaptor captor = ArgumentCaptor.forClass( Neo4jException.class ); verify( responseHandler ).onFailure( captor.capture() ); @@ -115,7 +117,7 @@ public void shouldReadRecordMessage() messageDispatcher.queue( responseHandler ); Value[] fields = {value( 1 ), value( 2 ), value( 3 )}; - channel.writeInbound( asByteBuf( new RecordMessage( fields ) ) ); + channel.writeInbound( writer.asByteBuf( new RecordMessage( fields ) ) ); verify( responseHandler ).onRecord( fields ); } @@ -126,7 +128,7 @@ public void shouldReadIgnoredMessage() ResponseHandler responseHandler = mock( ResponseHandler.class ); messageDispatcher.queue( responseHandler ); - channel.writeInbound( asByteBuf( new IgnoredMessage() ) ); + channel.writeInbound( writer.asByteBuf( new IgnoredMessage() ) ); assertEquals( 0, messageDispatcher.queuedHandlersCount() ); } @@ -146,7 +148,7 @@ public void shouldRethrowReadErrors() throws IOException try { - channel.writeInbound( asByteBuf( RESET ) ); + channel.writeInbound( writer.asByteBuf( RESET ) ); fail( "Exception expected" ); } catch ( DecoderException e ) diff --git a/driver/src/test/java/org/neo4j/driver/internal/messaging/MessageFormatTest.java b/driver/src/test/java/org/neo4j/driver/internal/messaging/MessageFormatTest.java index 4b111ef25f..eca8a25d7d 100644 --- a/driver/src/test/java/org/neo4j/driver/internal/messaging/MessageFormatTest.java +++ b/driver/src/test/java/org/neo4j/driver/internal/messaging/MessageFormatTest.java @@ -32,7 +32,7 @@ import org.neo4j.driver.internal.InternalNode; import org.neo4j.driver.internal.InternalPath; import org.neo4j.driver.internal.InternalRelationship; -import org.neo4j.driver.internal.async.BoltProtocolV1Util; +import org.neo4j.driver.internal.async.BoltProtocolUtil; import org.neo4j.driver.internal.async.ChannelPipelineBuilderImpl; import org.neo4j.driver.internal.async.inbound.InboundMessageDispatcher; import org.neo4j.driver.internal.async.outbound.ChunkAwareByteBufOutput; @@ -121,7 +121,7 @@ public void shouldGiveHelpfulErrorOnMalformedNodeStruct() throws Throwable packer.packStructHeader( 0, PackStreamMessageFormatV1.NODE ); output.stop(); - BoltProtocolV1Util.writeMessageBoundary( buf ); + BoltProtocolUtil.writeMessageBoundary( buf ); // Expect exception.expect( ClientException.class ); diff --git a/driver/src/test/java/org/neo4j/driver/internal/messaging/PackStreamMessageFormatV2Test.java b/driver/src/test/java/org/neo4j/driver/internal/messaging/PackStreamMessageFormatV2Test.java new file mode 100644 index 0000000000..8b76bdecaa --- /dev/null +++ b/driver/src/test/java/org/neo4j/driver/internal/messaging/PackStreamMessageFormatV2Test.java @@ -0,0 +1,46 @@ +/* + * Copyright (c) 2002-2018 "Neo Technology," + * Network Engine for Objects in Lund AB [http://neotechnology.com] + * + * This file is part of Neo4j. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.neo4j.driver.internal.messaging; + +import org.junit.Test; + +import org.neo4j.driver.internal.packstream.PackOutput; + +import static org.junit.Assert.fail; +import static org.mockito.Mockito.mock; + +public class PackStreamMessageFormatV2Test +{ + private final PackStreamMessageFormatV2 messageFormat = new PackStreamMessageFormatV2(); + + @Test + public void shouldFailToCreateWriterWithoutByteArraySupport() + { + PackOutput output = mock( PackOutput.class ); + + try + { + messageFormat.newWriter( output, false ); + fail( "Exception expected" ); + } + catch ( IllegalArgumentException ignore ) + { + } + } +} diff --git a/driver/src/test/java/org/neo4j/driver/internal/util/FailingMessageFormat.java b/driver/src/test/java/org/neo4j/driver/internal/util/FailingMessageFormat.java index 26400ebebe..670d1629e8 100644 --- a/driver/src/test/java/org/neo4j/driver/internal/util/FailingMessageFormat.java +++ b/driver/src/test/java/org/neo4j/driver/internal/util/FailingMessageFormat.java @@ -75,12 +75,6 @@ public Reader newReader( PackInput input ) return new ThrowingReader( delegate.newReader( input ), readerThrowableRef, readerFailureRef ); } - @Override - public int version() - { - return delegate.version(); - } - private static class ThrowingWriter implements MessageFormat.Writer { final MessageFormat.Writer delegate; @@ -93,7 +87,7 @@ private static class ThrowingWriter implements MessageFormat.Writer } @Override - public Writer write( Message msg ) throws IOException + public void write( Message msg ) throws IOException { Throwable error = throwableRef.getAndSet( null ); if ( error != null ) @@ -102,9 +96,8 @@ public Writer write( Message msg ) throws IOException } else { - return delegate.write( msg ); + delegate.write( msg ); } - return this; } } diff --git a/driver/src/test/java/org/neo4j/driver/internal/util/MessageToByteBufWriter.java b/driver/src/test/java/org/neo4j/driver/internal/util/MessageToByteBufWriter.java index 97186a4f99..c74e7b673c 100644 --- a/driver/src/test/java/org/neo4j/driver/internal/util/MessageToByteBufWriter.java +++ b/driver/src/test/java/org/neo4j/driver/internal/util/MessageToByteBufWriter.java @@ -24,22 +24,25 @@ import java.io.IOException; import org.neo4j.driver.internal.messaging.Message; -import org.neo4j.driver.internal.messaging.PackStreamMessageFormatV1; +import org.neo4j.driver.internal.messaging.MessageFormat; import org.neo4j.driver.internal.packstream.PackOutput; -public final class MessageToByteBufWriter +public class MessageToByteBufWriter { - private MessageToByteBufWriter() + private final MessageFormat messageFormat; + + public MessageToByteBufWriter( MessageFormat messageFormat ) { + this.messageFormat = messageFormat; } - public static ByteBuf asByteBuf( Message message ) + public ByteBuf asByteBuf( Message message ) { try { ByteBuf buf = Unpooled.buffer(); ByteBufOutput output = new ByteBufOutput( buf ); - new PackStreamMessageFormatV1.Writer( output, true ).write( message ); + messageFormat.newWriter( output, true ).write( message ); return buf; } catch ( IOException e ) diff --git a/driver/src/test/java/org/neo4j/driver/v1/integration/PointTypeIT.java b/driver/src/test/java/org/neo4j/driver/v1/integration/PointTypeIT.java new file mode 100644 index 0000000000..71566a7646 --- /dev/null +++ b/driver/src/test/java/org/neo4j/driver/v1/integration/PointTypeIT.java @@ -0,0 +1,118 @@ +/* + * Copyright (c) 2002-2018 "Neo Technology," + * Network Engine for Objects in Lund AB [http://neotechnology.com] + * + * This file is part of Neo4j. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.neo4j.driver.v1.integration; + +import org.junit.Rule; +import org.junit.Test; + +import java.util.concurrent.ThreadLocalRandom; +import java.util.stream.Stream; + +import org.neo4j.driver.v1.Record; +import org.neo4j.driver.v1.Value; +import org.neo4j.driver.v1.types.Point; +import org.neo4j.driver.v1.util.TestNeo4jSession; + +import static java.util.Arrays.asList; +import static java.util.Collections.singletonMap; +import static org.junit.Assert.assertEquals; +import static org.neo4j.driver.v1.Values.point; + +public class PointTypeIT +{ + private static final int EPSG_TABLE_ID = 1; + private static final int WGS_84_CRS_CODE = 4326; + + private static final int SR_ORG_TABLE_ID = 2; + private static final int CARTESIAN_CRS_CODE = 7203; + + @Rule + public final TestNeo4jSession session = new TestNeo4jSession(); + + @Test + public void shouldReceivePoint() + { + Record record = session.run( "RETURN point({x: 39.111748, y:-76.775635})" ).single(); + + Point point = record.get( 0 ).asPoint(); + + assertEquals( SR_ORG_TABLE_ID, point.crsTableId() ); + assertEquals( CARTESIAN_CRS_CODE, point.crsCode() ); + assertEquals( asList( 39.111748, -76.775635 ), point.coordinate().values() ); + } + + @Test + public void shouldSendPoint() + { + Value pointValue = point( EPSG_TABLE_ID, WGS_84_CRS_CODE, 38.8719, 77.0563 ); + Record record1 = session.run( "CREATE (n:Node {location: $point}) RETURN 42", singletonMap( "point", pointValue ) ).single(); + + assertEquals( 42, record1.get( 0 ).asInt() ); + + Record record2 = session.run( "MATCH (n:Node) RETURN n.location" ).single(); + Point point = record2.get( 0 ).asPoint(); + + assertEquals( EPSG_TABLE_ID, point.crsTableId() ); + assertEquals( WGS_84_CRS_CODE, point.crsCode() ); + assertEquals( asList( 38.8719, 77.0563 ), point.coordinate().values() ); + } + + @Test + public void shouldSendAndReceivePoint() + { + testPointSendAndReceive( SR_ORG_TABLE_ID, CARTESIAN_CRS_CODE, 40.7624, 73.9738 ); + } + + @Test + public void shouldSendAndReceiveRandomPoints() + { + Stream randomPoints = ThreadLocalRandom.current() + .ints( 1_000, 0, 2 ) + .mapToObj( idx -> idx % 2 == 0 + ? point( EPSG_TABLE_ID, WGS_84_CRS_CODE, randomCoordinate() ) + : point( SR_ORG_TABLE_ID, CARTESIAN_CRS_CODE, randomCoordinate() ) ); + + randomPoints.forEach( this::testPointSendAndReceive ); + } + + private void testPointSendAndReceive( long crsTableId, long crsCode, double... coordinate ) + { + testPointSendAndReceive( point( crsTableId, crsCode, coordinate ) ); + } + + private void testPointSendAndReceive( Value pointValue ) + { + Point originalPoint = pointValue.asPoint(); + + Record record = session.run( "CREATE (n {p:$point}) return n.p", singletonMap( "point", pointValue ) ).single(); + Point receivedPoint = record.get( 0 ).asPoint(); + + String message = "Failed for " + originalPoint; + assertEquals( message, originalPoint.crsTableId(), receivedPoint.crsTableId() ); + assertEquals( message, originalPoint.crsCode(), receivedPoint.crsCode() ); + assertEquals( message, originalPoint.coordinate().values(), receivedPoint.coordinate().values() ); + } + + private static double[] randomCoordinate() + { + ThreadLocalRandom random = ThreadLocalRandom.current(); + int count = random.nextInt( 2, 4 ); // either 2D or 3D point + return random.doubles( count, -180.0, 180 ).toArray(); + } +} From 4d1454435f3a229567e9bb2e68fcad6a82cfee6a Mon Sep 17 00:00:00 2001 From: lutovich Date: Fri, 19 Jan 2018 14:02:40 +0100 Subject: [PATCH 2/7] Execute point ITs only against neo4j 3.4+ --- .../org/neo4j/driver/internal/util/ServerVersion.java | 1 + .../org/neo4j/driver/v1/integration/PointTypeIT.java | 9 +++++++++ 2 files changed, 10 insertions(+) diff --git a/driver/src/main/java/org/neo4j/driver/internal/util/ServerVersion.java b/driver/src/main/java/org/neo4j/driver/internal/util/ServerVersion.java index 686e5a386b..1b3fee7608 100644 --- a/driver/src/main/java/org/neo4j/driver/internal/util/ServerVersion.java +++ b/driver/src/main/java/org/neo4j/driver/internal/util/ServerVersion.java @@ -28,6 +28,7 @@ public class ServerVersion { + public static final ServerVersion v3_4_0 = new ServerVersion( 3, 4, 0 ); public static final ServerVersion v3_2_0 = new ServerVersion( 3, 2, 0 ); public static final ServerVersion v3_1_0 = new ServerVersion( 3, 1, 0 ); public static final ServerVersion v3_0_0 = new ServerVersion( 3, 0, 0 ); diff --git a/driver/src/test/java/org/neo4j/driver/v1/integration/PointTypeIT.java b/driver/src/test/java/org/neo4j/driver/v1/integration/PointTypeIT.java index 71566a7646..118d050c7e 100644 --- a/driver/src/test/java/org/neo4j/driver/v1/integration/PointTypeIT.java +++ b/driver/src/test/java/org/neo4j/driver/v1/integration/PointTypeIT.java @@ -18,6 +18,7 @@ */ package org.neo4j.driver.v1.integration; +import org.junit.Before; import org.junit.Rule; import org.junit.Test; @@ -32,6 +33,8 @@ import static java.util.Arrays.asList; import static java.util.Collections.singletonMap; import static org.junit.Assert.assertEquals; +import static org.junit.Assume.assumeTrue; +import static org.neo4j.driver.internal.util.ServerVersion.v3_4_0; import static org.neo4j.driver.v1.Values.point; public class PointTypeIT @@ -45,6 +48,12 @@ public class PointTypeIT @Rule public final TestNeo4jSession session = new TestNeo4jSession(); + @Before + public void setUp() + { + assumeTrue( session.version().greaterThanOrEqual( v3_4_0 ) ); + } + @Test public void shouldReceivePoint() { From 26fd3347975b8267341c540c266aad0f9437ffa0 Mon Sep 17 00:00:00 2001 From: lutovich Date: Fri, 9 Feb 2018 15:08:26 +0100 Subject: [PATCH 3/7] New types for 2D and 3D points Introduced new types for points instead of a single generic interface that represents an arbitrary-dimensional point. This makes it cleaner what kind of points are supported by the database. It also removes the need for coordinate array creation and removes the `Coordinate` interface all together. --- .../neo4j/driver/internal/InternalPoint.java | 86 ------------------- .../driver/internal/InternalPoint2D.java | 53 ++++++++++++ ...alCoordinate.java => InternalPoint3D.java} | 45 ++++------ .../messaging/PackStreamMessageFormatV2.java | 75 +++++++++------- .../ByteArrayIncompatiblePacker.java | 3 +- .../internal/packstream/PackStream.java | 35 +++----- .../internal/types/InternalTypeSystem.java | 16 +++- .../internal/types/TypeConstructor.java | 11 ++- .../{PointValue.java => Point2DValue.java} | 12 +-- .../driver/internal/value/Point3DValue.java | 57 ++++++++++++ .../driver/internal/value/ValueAdapter.java | 13 ++- .../main/java/org/neo4j/driver/v1/Value.java | 7 +- .../main/java/org/neo4j/driver/v1/Values.java | 45 ++++++---- .../types/{Coordinate.java => Point2D.java} | 10 ++- .../v1/types/{Point.java => Point3D.java} | 10 ++- .../org/neo4j/driver/v1/types/TypeSystem.java | 4 +- .../internal/packstream/PackStreamTest.java | 63 -------------- 17 files changed, 273 insertions(+), 272 deletions(-) delete mode 100644 driver/src/main/java/org/neo4j/driver/internal/InternalPoint.java create mode 100644 driver/src/main/java/org/neo4j/driver/internal/InternalPoint2D.java rename driver/src/main/java/org/neo4j/driver/internal/{InternalCoordinate.java => InternalPoint3D.java} (51%) rename driver/src/main/java/org/neo4j/driver/internal/value/{PointValue.java => Point2DValue.java} (82%) create mode 100644 driver/src/main/java/org/neo4j/driver/internal/value/Point3DValue.java rename driver/src/main/java/org/neo4j/driver/v1/types/{Coordinate.java => Point2D.java} (91%) rename driver/src/main/java/org/neo4j/driver/v1/types/{Point.java => Point3D.java} (89%) diff --git a/driver/src/main/java/org/neo4j/driver/internal/InternalPoint.java b/driver/src/main/java/org/neo4j/driver/internal/InternalPoint.java deleted file mode 100644 index d2670e786f..0000000000 --- a/driver/src/main/java/org/neo4j/driver/internal/InternalPoint.java +++ /dev/null @@ -1,86 +0,0 @@ -/* - * Copyright (c) 2002-2018 "Neo Technology," - * Network Engine for Objects in Lund AB [http://neotechnology.com] - * - * This file is part of Neo4j. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ -package org.neo4j.driver.internal; - -import org.neo4j.driver.v1.types.Coordinate; -import org.neo4j.driver.v1.types.Point; - -public class InternalPoint implements Point -{ - private final long crsTableId; - private final long crsCode; - private final Coordinate coordinate; - - public InternalPoint( long crsTableId, long crsCode, Coordinate coordinate ) - { - this.crsTableId = crsTableId; - this.crsCode = crsCode; - this.coordinate = coordinate; - } - - @Override - public long crsTableId() - { - return crsTableId; - } - - @Override - public long crsCode() - { - return crsCode; - } - - @Override - public Coordinate coordinate() - { - return coordinate; - } - - @Override - public boolean equals( Object o ) - { - if ( this == o ) - { - return true; - } - if ( o == null || getClass() != o.getClass() ) - { - return false; - } - InternalPoint that = (InternalPoint) o; - return crsTableId == that.crsTableId && - crsCode == that.crsCode && - coordinate.equals( that.coordinate ); - } - - @Override - public int hashCode() - { - int result = (int) (crsTableId ^ (crsTableId >>> 32)); - result = 31 * result + (int) (crsCode ^ (crsCode >>> 32)); - result = 31 * result + coordinate.hashCode(); - return result; - } - - @Override - public String toString() - { - return String.format( "Point<%s, %s, %s>", crsTableId, crsCode, coordinate ); - } -} diff --git a/driver/src/main/java/org/neo4j/driver/internal/InternalPoint2D.java b/driver/src/main/java/org/neo4j/driver/internal/InternalPoint2D.java new file mode 100644 index 0000000000..8b236a0f5f --- /dev/null +++ b/driver/src/main/java/org/neo4j/driver/internal/InternalPoint2D.java @@ -0,0 +1,53 @@ +/* + * Copyright (c) 2002-2018 "Neo Technology," + * Network Engine for Objects in Lund AB [http://neotechnology.com] + * + * This file is part of Neo4j. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.neo4j.driver.internal; + +import org.neo4j.driver.v1.types.Point2D; + +public class InternalPoint2D implements Point2D +{ + private final long srid; + private final double x; + private final double y; + + public InternalPoint2D( long srid, double x, double y ) + { + this.srid = srid; + this.x = x; + this.y = y; + } + + @Override + public long srid() + { + return srid; + } + + @Override + public double x() + { + return x; + } + + @Override + public double y() + { + return y; + } +} diff --git a/driver/src/main/java/org/neo4j/driver/internal/InternalCoordinate.java b/driver/src/main/java/org/neo4j/driver/internal/InternalPoint3D.java similarity index 51% rename from driver/src/main/java/org/neo4j/driver/internal/InternalCoordinate.java rename to driver/src/main/java/org/neo4j/driver/internal/InternalPoint3D.java index e7c7dff611..a53cce8396 100644 --- a/driver/src/main/java/org/neo4j/driver/internal/InternalCoordinate.java +++ b/driver/src/main/java/org/neo4j/driver/internal/InternalPoint3D.java @@ -18,51 +18,44 @@ */ package org.neo4j.driver.internal; -import java.util.List; +import org.neo4j.driver.v1.types.Point3D; -import org.neo4j.driver.v1.types.Coordinate; - -import static java.util.Collections.unmodifiableList; - -public class InternalCoordinate implements Coordinate +public class InternalPoint3D implements Point3D { - private final List values; + private final long srid; + private final double x; + private final double y; + private final double z; - public InternalCoordinate( List values ) + public InternalPoint3D( long srid, double x, double y, double z ) { - this.values = unmodifiableList( values ); + this.srid = srid; + this.x = x; + this.y = y; + this.z = z; } @Override - public List values() + public long srid() { - return values; + return srid; } @Override - public boolean equals( Object o ) + public double x() { - if ( this == o ) - { - return true; - } - if ( o == null || getClass() != o.getClass() ) - { - return false; - } - InternalCoordinate that = (InternalCoordinate) o; - return values.equals( that.values ); + return x; } @Override - public int hashCode() + public double y() { - return values.hashCode(); + return y; } @Override - public String toString() + public double z() { - return String.format( "Coordinate<%s>", values ); + return z; } } diff --git a/driver/src/main/java/org/neo4j/driver/internal/messaging/PackStreamMessageFormatV2.java b/driver/src/main/java/org/neo4j/driver/internal/messaging/PackStreamMessageFormatV2.java index 53644b85d1..919852547b 100644 --- a/driver/src/main/java/org/neo4j/driver/internal/messaging/PackStreamMessageFormatV2.java +++ b/driver/src/main/java/org/neo4j/driver/internal/messaging/PackStreamMessageFormatV2.java @@ -19,24 +19,26 @@ package org.neo4j.driver.internal.messaging; import java.io.IOException; -import java.util.ArrayList; -import java.util.List; -import org.neo4j.driver.internal.InternalCoordinate; -import org.neo4j.driver.internal.InternalPoint; +import org.neo4j.driver.internal.InternalPoint2D; +import org.neo4j.driver.internal.InternalPoint3D; import org.neo4j.driver.internal.packstream.PackInput; import org.neo4j.driver.internal.packstream.PackOutput; import org.neo4j.driver.internal.value.InternalValue; -import org.neo4j.driver.internal.value.PointValue; +import org.neo4j.driver.internal.value.Point2DValue; +import org.neo4j.driver.internal.value.Point3DValue; import org.neo4j.driver.v1.Value; -import org.neo4j.driver.v1.types.Point; +import org.neo4j.driver.v1.types.Point2D; +import org.neo4j.driver.v1.types.Point3D; -import static org.neo4j.driver.internal.types.TypeConstructor.POINT_TyCon; +import static org.neo4j.driver.internal.types.TypeConstructor.POINT_2D_TyCon; +import static org.neo4j.driver.internal.types.TypeConstructor.POINT_3D_TyCon; public class PackStreamMessageFormatV2 extends PackStreamMessageFormatV1 { - private static final byte POINT_STRUCT_TYPE = 'X'; - private static final int POINT_STRUCT_SIZE = 3; + private static final byte POINT_2D_STRUCT_TYPE = 'X'; + private static final byte POINT_3D_STRUCT_TYPE = 'Y'; + private static final int POINT_STRUCT_SIZE = 2; @Override public MessageFormat.Writer newWriter( PackOutput output, boolean byteArraySupportEnabled ) @@ -64,9 +66,13 @@ private static class WriterV2 extends WriterV1 @Override void packInternalValue( InternalValue value ) throws IOException { - if ( value.typeConstructor() == POINT_TyCon ) + if ( value.typeConstructor() == POINT_2D_TyCon ) { - packPoint( value.asPoint() ); + packPoint2D( value.asPoint2D() ); + } + else if ( value.typeConstructor() == POINT_3D_TyCon ) + { + packPoint3D( value.asPoint3D() ); } else { @@ -74,12 +80,18 @@ void packInternalValue( InternalValue value ) throws IOException } } - private void packPoint( Point point ) throws IOException + private void packPoint2D( Point2D point ) throws IOException + { + packer.packStructHeader( POINT_STRUCT_SIZE, POINT_2D_STRUCT_TYPE ); + packer.pack( point.srid() ); +// packer.pack( point.x(), point.y() ); + } + + private void packPoint3D( Point3D point ) throws IOException { - packer.packStructHeader( POINT_STRUCT_SIZE, POINT_STRUCT_TYPE ); - packer.pack( point.crsTableId() ); - packer.pack( point.crsCode() ); - packer.pack( point.coordinate().values() ); + packer.packStructHeader( POINT_STRUCT_SIZE, POINT_3D_STRUCT_TYPE ); + packer.pack( point.srid() ); +// packer.pack( point.x(), point.y(), point.z() ); } } @@ -93,10 +105,15 @@ private static class ReaderV2 extends ReaderV1 @Override Value unpackStruct( long size, byte type ) throws IOException { - if ( type == POINT_STRUCT_TYPE ) + if ( type == POINT_2D_STRUCT_TYPE ) + { + ensureCorrectStructSize( POINT_2D_TyCon.typeName(), POINT_STRUCT_SIZE, size ); + return unpackPoint2D(); + } + else if ( type == POINT_3D_STRUCT_TYPE ) { - ensureCorrectStructSize( POINT_TyCon.typeName(), POINT_STRUCT_SIZE, size ); - return unpackPoint(); + ensureCorrectStructSize( POINT_3D_TyCon.typeName(), POINT_STRUCT_SIZE, size ); + return unpackPoint3D(); } else { @@ -104,20 +121,16 @@ Value unpackStruct( long size, byte type ) throws IOException } } - private Value unpackPoint() throws IOException + private Value unpackPoint2D() throws IOException { - long crsTableId = unpacker.unpackLong(); - long crsCode = unpacker.unpackLong(); - - int coordinateSize = (int) unpacker.unpackListHeader(); - List coordinate = new ArrayList<>( coordinateSize ); - for ( int i = 0; i < coordinateSize; i++ ) - { - coordinate.add( unpacker.unpackDouble() ); - } + long srid = unpacker.unpackLong(); + return new Point2DValue( new InternalPoint2D( srid, 0.0, 0.0 ) ); + } - Point point = new InternalPoint( crsTableId, crsCode, new InternalCoordinate( coordinate ) ); - return new PointValue( point ); + private Value unpackPoint3D() throws IOException + { + long srid = unpacker.unpackLong(); + return new Point3DValue( new InternalPoint3D( srid, 0.0, 0.0, 0.0 ) ); } } } diff --git a/driver/src/main/java/org/neo4j/driver/internal/packstream/ByteArrayIncompatiblePacker.java b/driver/src/main/java/org/neo4j/driver/internal/packstream/ByteArrayIncompatiblePacker.java index 3b16adef3d..7ff2649d96 100644 --- a/driver/src/main/java/org/neo4j/driver/internal/packstream/ByteArrayIncompatiblePacker.java +++ b/driver/src/main/java/org/neo4j/driver/internal/packstream/ByteArrayIncompatiblePacker.java @@ -27,9 +27,10 @@ public ByteArrayIncompatiblePacker( PackOutput out ) super( out ); } + @Override public void packBytesHeader( int size ) throws IOException { throw new PackStream.UnPackable( "Packing bytes is not supported " + - "as the current server this driver connected to does not support unpack bytes." ); + "as the current server this driver connected to does not support unpack bytes." ); } } diff --git a/driver/src/main/java/org/neo4j/driver/internal/packstream/PackStream.java b/driver/src/main/java/org/neo4j/driver/internal/packstream/PackStream.java index 6e931d0df1..0529931a8d 100644 --- a/driver/src/main/java/org/neo4j/driver/internal/packstream/PackStream.java +++ b/driver/src/main/java/org/neo4j/driver/internal/packstream/PackStream.java @@ -159,7 +159,7 @@ public Packer( PackOutput out ) this.out = out; } - public void packRaw( byte[] data ) throws IOException + private void packRaw( byte[] data ) throws IOException { out.writeBytes( data ); } @@ -174,11 +174,6 @@ public void pack( boolean value ) throws IOException out.writeByte( value ? TRUE : FALSE ); } - public void pack( char value ) throws IOException - { - pack( String.valueOf(value) ); - } - public void pack( long value ) throws IOException { if ( value >= MINUS_2_TO_THE_4 && value < PLUS_2_TO_THE_7) @@ -234,17 +229,7 @@ public void pack( String value ) throws IOException } } - public void packString( byte[] utf8 ) throws IOException - { - if ( utf8 == null ) { packNull(); } - else - { - packStringHeader( utf8.length ); - packRaw( utf8 ); - } - } - - public void pack( List values ) throws IOException + private void pack( List values ) throws IOException { if ( values == null ) { packNull(); } else @@ -257,7 +242,7 @@ public void pack( List values ) throws IOException } } - public void pack( Map values ) throws IOException + private void pack( Map values ) throws IOException { if ( values == null ) { packNull(); } else @@ -316,7 +301,7 @@ else if ( size < PLUS_2_TO_THE_16 ) } } - public void packStringHeader( int size ) throws IOException + private void packStringHeader( int size ) throws IOException { if ( size < 0x10 ) { @@ -662,17 +647,17 @@ public PackType peekNextType() throws IOException } } - public static class PackstreamException extends IOException + public static class PackStreamException extends IOException { private static final long serialVersionUID = -1491422133282345421L; - protected PackstreamException( String message ) + protected PackStreamException( String message ) { super( message ); } } - public static class EndOfStream extends PackstreamException + public static class EndOfStream extends PackStreamException { private static final long serialVersionUID = 5102836237108105603L; @@ -682,7 +667,7 @@ public EndOfStream( String message ) } } - public static class Overflow extends PackstreamException + public static class Overflow extends PackStreamException { private static final long serialVersionUID = -923071934446993659L; @@ -692,7 +677,7 @@ public Overflow( String message ) } } - public static class Unexpected extends PackstreamException + public static class Unexpected extends PackStreamException { private static final long serialVersionUID = 5004685868740125469L; @@ -702,7 +687,7 @@ public Unexpected( String message ) } } - public static class UnPackable extends PackstreamException + public static class UnPackable extends PackStreamException { private static final long serialVersionUID = 2408740707769711365L; diff --git a/driver/src/main/java/org/neo4j/driver/internal/types/InternalTypeSystem.java b/driver/src/main/java/org/neo4j/driver/internal/types/InternalTypeSystem.java index 7cceacfd34..d184dee9aa 100644 --- a/driver/src/main/java/org/neo4j/driver/internal/types/InternalTypeSystem.java +++ b/driver/src/main/java/org/neo4j/driver/internal/types/InternalTypeSystem.java @@ -33,7 +33,8 @@ import static org.neo4j.driver.internal.types.TypeConstructor.NULL_TyCon; import static org.neo4j.driver.internal.types.TypeConstructor.NUMBER_TyCon; import static org.neo4j.driver.internal.types.TypeConstructor.PATH_TyCon; -import static org.neo4j.driver.internal.types.TypeConstructor.POINT_TyCon; +import static org.neo4j.driver.internal.types.TypeConstructor.POINT_2D_TyCon; +import static org.neo4j.driver.internal.types.TypeConstructor.POINT_3D_TyCon; import static org.neo4j.driver.internal.types.TypeConstructor.RELATIONSHIP_TyCon; import static org.neo4j.driver.internal.types.TypeConstructor.STRING_TyCon; @@ -59,7 +60,8 @@ public class InternalTypeSystem implements TypeSystem private final TypeRepresentation nodeType = constructType( NODE_TyCon ); private final TypeRepresentation relationshipType = constructType( RELATIONSHIP_TyCon ); private final TypeRepresentation pathType = constructType( PATH_TyCon ); - private final TypeRepresentation pointType = constructType( POINT_TyCon ); + private final TypeRepresentation point2dType = constructType( POINT_2D_TyCon ); + private final TypeRepresentation point3dType = constructType( POINT_3D_TyCon ); private final TypeRepresentation nullType = constructType( NULL_TyCon ); private InternalTypeSystem() @@ -151,9 +153,15 @@ public Type PATH() } @Override - public Type POINT() + public Type POINT_2D() { - return pointType; + return point2dType; + } + + @Override + public Type POINT_3D() + { + return point3dType; } /** the Cypher type NULL */ diff --git a/driver/src/main/java/org/neo4j/driver/internal/types/TypeConstructor.java b/driver/src/main/java/org/neo4j/driver/internal/types/TypeConstructor.java index 51d0914587..0a037f40d1 100644 --- a/driver/src/main/java/org/neo4j/driver/internal/types/TypeConstructor.java +++ b/driver/src/main/java/org/neo4j/driver/internal/types/TypeConstructor.java @@ -139,7 +139,16 @@ public String typeName() } }, - POINT_TyCon + POINT_2D_TyCon + { + @Override + public String typeName() + { + return "POINT"; + } + }, + + POINT_3D_TyCon { @Override public String typeName() diff --git a/driver/src/main/java/org/neo4j/driver/internal/value/PointValue.java b/driver/src/main/java/org/neo4j/driver/internal/value/Point2DValue.java similarity index 82% rename from driver/src/main/java/org/neo4j/driver/internal/value/PointValue.java rename to driver/src/main/java/org/neo4j/driver/internal/value/Point2DValue.java index 71828c2d86..75afa39d82 100644 --- a/driver/src/main/java/org/neo4j/driver/internal/value/PointValue.java +++ b/driver/src/main/java/org/neo4j/driver/internal/value/Point2DValue.java @@ -19,20 +19,20 @@ package org.neo4j.driver.internal.value; import org.neo4j.driver.internal.types.InternalTypeSystem; -import org.neo4j.driver.v1.types.Point; +import org.neo4j.driver.v1.types.Point2D; import org.neo4j.driver.v1.types.Type; -public class PointValue extends ValueAdapter +public class Point2DValue extends ValueAdapter { - private final Point point; + private final Point2D point; - public PointValue( Point point ) + public Point2DValue( Point2D point ) { this.point = point; } @Override - public Point asPoint() + public Point2D asPoint2D() { return point; } @@ -52,6 +52,6 @@ public String toString( Format valueFormat ) @Override public Type type() { - return InternalTypeSystem.TYPE_SYSTEM.POINT(); + return InternalTypeSystem.TYPE_SYSTEM.POINT_2D(); } } diff --git a/driver/src/main/java/org/neo4j/driver/internal/value/Point3DValue.java b/driver/src/main/java/org/neo4j/driver/internal/value/Point3DValue.java new file mode 100644 index 0000000000..4b49f58f1e --- /dev/null +++ b/driver/src/main/java/org/neo4j/driver/internal/value/Point3DValue.java @@ -0,0 +1,57 @@ +/* + * Copyright (c) 2002-2018 "Neo Technology," + * Network Engine for Objects in Lund AB [http://neotechnology.com] + * + * This file is part of Neo4j. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.neo4j.driver.internal.value; + +import org.neo4j.driver.internal.types.InternalTypeSystem; +import org.neo4j.driver.v1.types.Point3D; +import org.neo4j.driver.v1.types.Type; + +public class Point3DValue extends ValueAdapter +{ + private final Point3D point; + + public Point3DValue( Point3D point ) + { + this.point = point; + } + + @Override + public Point3D asPoint3D() + { + return point; + } + + @Override + public Object asObject() + { + return point; + } + + @Override + public String toString( Format valueFormat ) + { + return maybeWithType( valueFormat.includeType(), point.toString() ); + } + + @Override + public Type type() + { + return InternalTypeSystem.TYPE_SYSTEM.POINT_3D(); + } +} diff --git a/driver/src/main/java/org/neo4j/driver/internal/value/ValueAdapter.java b/driver/src/main/java/org/neo4j/driver/internal/value/ValueAdapter.java index cd5fd2a661..7e976e92b6 100644 --- a/driver/src/main/java/org/neo4j/driver/internal/value/ValueAdapter.java +++ b/driver/src/main/java/org/neo4j/driver/internal/value/ValueAdapter.java @@ -31,7 +31,8 @@ import org.neo4j.driver.v1.types.Entity; import org.neo4j.driver.v1.types.Node; import org.neo4j.driver.v1.types.Path; -import org.neo4j.driver.v1.types.Point; +import org.neo4j.driver.v1.types.Point2D; +import org.neo4j.driver.v1.types.Point3D; import org.neo4j.driver.v1.types.Relationship; import org.neo4j.driver.v1.types.Type; import org.neo4j.driver.v1.util.Function; @@ -188,9 +189,15 @@ public Relationship asRelationship() } @Override - public Point asPoint() + public Point2D asPoint2D() { - throw new Uncoercible( type().name(), "Point" ); + throw new Uncoercible( type().name(), "Point2D" ); + } + + @Override + public Point3D asPoint3D() + { + throw new Uncoercible( type().name(), "Point3D" ); } @Override diff --git a/driver/src/main/java/org/neo4j/driver/v1/Value.java b/driver/src/main/java/org/neo4j/driver/v1/Value.java index 448bdbb92b..f974285711 100644 --- a/driver/src/main/java/org/neo4j/driver/v1/Value.java +++ b/driver/src/main/java/org/neo4j/driver/v1/Value.java @@ -29,7 +29,8 @@ import org.neo4j.driver.v1.types.MapAccessorWithDefaultValue; import org.neo4j.driver.v1.types.Node; import org.neo4j.driver.v1.types.Path; -import org.neo4j.driver.v1.types.Point; +import org.neo4j.driver.v1.types.Point2D; +import org.neo4j.driver.v1.types.Point3D; import org.neo4j.driver.v1.types.Relationship; import org.neo4j.driver.v1.types.Type; import org.neo4j.driver.v1.types.TypeSystem; @@ -290,7 +291,9 @@ public interface Value extends MapAccessor, MapAccessorWithDefaultValue */ Path asPath(); - Point asPoint(); + Point2D asPoint2D(); + + Point3D asPoint3D(); // Force implementation @Override diff --git a/driver/src/main/java/org/neo4j/driver/v1/Values.java b/driver/src/main/java/org/neo4j/driver/v1/Values.java index 678380fb23..e52fc54a52 100644 --- a/driver/src/main/java/org/neo4j/driver/v1/Values.java +++ b/driver/src/main/java/org/neo4j/driver/v1/Values.java @@ -26,11 +26,10 @@ import java.util.Iterator; import java.util.List; import java.util.Map; -import java.util.stream.DoubleStream; import org.neo4j.driver.internal.AsValue; -import org.neo4j.driver.internal.InternalCoordinate; -import org.neo4j.driver.internal.InternalPoint; +import org.neo4j.driver.internal.InternalPoint2D; +import org.neo4j.driver.internal.InternalPoint3D; import org.neo4j.driver.internal.value.BooleanValue; import org.neo4j.driver.internal.value.BytesValue; import org.neo4j.driver.internal.value.FloatValue; @@ -38,18 +37,19 @@ import org.neo4j.driver.internal.value.ListValue; import org.neo4j.driver.internal.value.MapValue; import org.neo4j.driver.internal.value.NullValue; -import org.neo4j.driver.internal.value.PointValue; +import org.neo4j.driver.internal.value.Point2DValue; +import org.neo4j.driver.internal.value.Point3DValue; import org.neo4j.driver.internal.value.StringValue; import org.neo4j.driver.v1.exceptions.ClientException; import org.neo4j.driver.v1.types.Entity; import org.neo4j.driver.v1.types.Node; import org.neo4j.driver.v1.types.Path; -import org.neo4j.driver.v1.types.Point; +import org.neo4j.driver.v1.types.Point2D; +import org.neo4j.driver.v1.types.Point3D; import org.neo4j.driver.v1.types.Relationship; import org.neo4j.driver.v1.types.TypeSystem; import org.neo4j.driver.v1.util.Function; -import static java.util.stream.Collectors.toList; import static org.neo4j.driver.internal.util.Iterables.newHashMapWithSize; /** @@ -263,11 +263,14 @@ public static Value value( final Map val ) return new MapValue( asValues ); } - public static Value point( long crsTableId, long crsCode, double... coordinates ) + public static Value point2D( int srid, double x, double y ) { - InternalCoordinate coordinate = new InternalCoordinate( DoubleStream.of( coordinates ).boxed().collect( toList() ) ); - InternalPoint point = new InternalPoint( crsTableId, crsCode, coordinate ); - return new PointValue( point ); + return new Point2DValue( new InternalPoint2D( srid, x, y ) ); + } + + public static Value point3D( int srid, double x, double y, double z ) + { + return new Point3DValue( new InternalPoint3D( srid, x, y, z ) ); } /** @@ -485,9 +488,14 @@ public static Function ofPath() return PATH; } - public static Function ofPoint() + public static Function ofPoint2D() + { + return POINT_2D; + } + + public static Function ofPoint3D() { - return POINT; + return POINT_3D; } /** @@ -637,11 +645,18 @@ public Path apply( Value val ) return val.asPath(); } }; - private static final Function POINT = new Function() + private static final Function POINT_2D = new Function() + { + public Point2D apply( Value val ) + { + return val.asPoint2D(); + } + }; + private static final Function POINT_3D = new Function() { - public Point apply( Value val ) + public Point3D apply( Value val ) { - return val.asPoint(); + return val.asPoint3D(); } }; diff --git a/driver/src/main/java/org/neo4j/driver/v1/types/Coordinate.java b/driver/src/main/java/org/neo4j/driver/v1/types/Point2D.java similarity index 91% rename from driver/src/main/java/org/neo4j/driver/v1/types/Coordinate.java rename to driver/src/main/java/org/neo4j/driver/v1/types/Point2D.java index b5d3a1fe38..a452fe1a23 100644 --- a/driver/src/main/java/org/neo4j/driver/v1/types/Coordinate.java +++ b/driver/src/main/java/org/neo4j/driver/v1/types/Point2D.java @@ -18,12 +18,14 @@ */ package org.neo4j.driver.v1.types; -import java.util.List; - import org.neo4j.driver.v1.util.Experimental; @Experimental -public interface Coordinate +public interface Point2D { - List values(); + long srid(); + + double x(); + + double y(); } diff --git a/driver/src/main/java/org/neo4j/driver/v1/types/Point.java b/driver/src/main/java/org/neo4j/driver/v1/types/Point3D.java similarity index 89% rename from driver/src/main/java/org/neo4j/driver/v1/types/Point.java rename to driver/src/main/java/org/neo4j/driver/v1/types/Point3D.java index 26d3f99702..11b6cfce2d 100644 --- a/driver/src/main/java/org/neo4j/driver/v1/types/Point.java +++ b/driver/src/main/java/org/neo4j/driver/v1/types/Point3D.java @@ -21,11 +21,13 @@ import org.neo4j.driver.v1.util.Experimental; @Experimental -public interface Point +public interface Point3D { - long crsTableId(); + long srid(); - long crsCode(); + double x(); - Coordinate coordinate(); + double y(); + + double z(); } diff --git a/driver/src/main/java/org/neo4j/driver/v1/types/TypeSystem.java b/driver/src/main/java/org/neo4j/driver/v1/types/TypeSystem.java index 01c0aafeb2..3db423d053 100644 --- a/driver/src/main/java/org/neo4j/driver/v1/types/TypeSystem.java +++ b/driver/src/main/java/org/neo4j/driver/v1/types/TypeSystem.java @@ -53,7 +53,9 @@ public interface TypeSystem Type PATH(); - Type POINT(); + Type POINT_2D(); + + Type POINT_3D(); Type NULL(); } diff --git a/driver/src/test/java/org/neo4j/driver/internal/packstream/PackStreamTest.java b/driver/src/test/java/org/neo4j/driver/internal/packstream/PackStreamTest.java index d4881c9396..d4405a1dd3 100644 --- a/driver/src/test/java/org/neo4j/driver/internal/packstream/PackStreamTest.java +++ b/driver/src/test/java/org/neo4j/driver/internal/packstream/PackStreamTest.java @@ -34,7 +34,6 @@ import org.neo4j.driver.internal.util.Iterables; -import static java.nio.charset.StandardCharsets.UTF_8; import static java.util.Arrays.asList; import static org.hamcrest.CoreMatchers.equalTo; import static org.hamcrest.MatcherAssert.assertThat; @@ -375,25 +374,6 @@ public void testCanPackAndUnpackBytes() throws Throwable } - @Test - public void testCanPackAndUnpackChar() throws Throwable - { - // Given - Machine machine = new Machine(); - - // When - PackStream.Packer packer = machine.packer(); - packer.pack( 'A' ); - - // Then - PackStream.Unpacker unpacker = newUnpacker( machine.output() ); - PackType packType = unpacker.peekNextType(); - - // Then - assertThat( packType, equalTo( PackType.STRING ) ); - assertThat( unpacker.unpackString(), equalTo( "A" )); - } - @Test public void testCanPackAndUnpackString() throws Throwable { @@ -434,49 +414,6 @@ public void testCanPackAndUnpackSpecialString() throws Throwable assertThat( unpacker.unpackString(), equalTo( code )); } - @Test - public void testCanPackAndUnpackStringFromBytes() throws Throwable - { - // Given - Machine machine = new Machine(); - - // When - PackStream.Packer packer = machine.packer(); - packer.packString( "ABCDEFGHIJ".getBytes() ); - - // Then - PackStream.Unpacker unpacker = newUnpacker( machine.output() ); - PackType packType = unpacker.peekNextType(); - - // Then - assertThat( packType, equalTo( PackType.STRING ) ); - assertThat( unpacker.unpackString(), equalTo( "ABCDEFGHIJ" )); - - } - - @Test - public void testCanPackAndUnpackSpecialStringFromBytes() throws Throwable - { - // Given - Machine machine = new Machine(); - String code = "Mjölnir"; // UTF-8 `c3 b6` for ö - - // When - PackStream.Packer packer = machine.packer(); - - byte[] bytes = code.getBytes( UTF_8 ); - - packer.packString( bytes ); - - // Then - PackStream.Unpacker unpacker = newUnpacker( machine.output() ); - PackType packType = unpacker.peekNextType(); - - // Then - assertThat( packType, equalTo( PackType.STRING ) ); - assertThat( unpacker.unpackString(), equalTo( code )); - } - @Test public void testCanPackAndUnpackListOneItemAtATime() throws Throwable { From 701dc0ab4d03e32bd7916a4c3ecd1a5d9a019306 Mon Sep 17 00:00:00 2001 From: lutovich Date: Mon, 19 Feb 2018 17:36:56 +0100 Subject: [PATCH 4/7] Encode 2D and 3D point coordinates as doubles --- .../internal/async/inbound/ByteBufInput.java | 3 +- .../messaging/PackStreamMessageFormatV2.java | 28 ++++++--- .../driver/internal/packstream/PackInput.java | 2 +- .../main/java/org/neo4j/driver/v1/Values.java | 4 +- .../packstream/BufferedChannelInput.java | 3 +- .../{PointTypeIT.java => PointsIT.java} | 60 ++++++++----------- 6 files changed, 49 insertions(+), 51 deletions(-) rename driver/src/test/java/org/neo4j/driver/v1/integration/{PointTypeIT.java => PointsIT.java} (55%) diff --git a/driver/src/main/java/org/neo4j/driver/internal/async/inbound/ByteBufInput.java b/driver/src/main/java/org/neo4j/driver/internal/async/inbound/ByteBufInput.java index e274b4f706..521e4249e5 100644 --- a/driver/src/main/java/org/neo4j/driver/internal/async/inbound/ByteBufInput.java +++ b/driver/src/main/java/org/neo4j/driver/internal/async/inbound/ByteBufInput.java @@ -70,10 +70,9 @@ public double readDouble() } @Override - public PackInput readBytes( byte[] into, int offset, int toRead ) + public void readBytes( byte[] into, int offset, int toRead ) { buf.readBytes( into, offset, toRead ); - return this; } @Override diff --git a/driver/src/main/java/org/neo4j/driver/internal/messaging/PackStreamMessageFormatV2.java b/driver/src/main/java/org/neo4j/driver/internal/messaging/PackStreamMessageFormatV2.java index 919852547b..0cd8f90ab4 100644 --- a/driver/src/main/java/org/neo4j/driver/internal/messaging/PackStreamMessageFormatV2.java +++ b/driver/src/main/java/org/neo4j/driver/internal/messaging/PackStreamMessageFormatV2.java @@ -38,7 +38,9 @@ public class PackStreamMessageFormatV2 extends PackStreamMessageFormatV1 { private static final byte POINT_2D_STRUCT_TYPE = 'X'; private static final byte POINT_3D_STRUCT_TYPE = 'Y'; - private static final int POINT_STRUCT_SIZE = 2; + + private static final int POINT_2D_STRUCT_SIZE = 3; + private static final int POINT_3D_STRUCT_SIZE = 4; @Override public MessageFormat.Writer newWriter( PackOutput output, boolean byteArraySupportEnabled ) @@ -82,16 +84,19 @@ else if ( value.typeConstructor() == POINT_3D_TyCon ) private void packPoint2D( Point2D point ) throws IOException { - packer.packStructHeader( POINT_STRUCT_SIZE, POINT_2D_STRUCT_TYPE ); + packer.packStructHeader( POINT_2D_STRUCT_SIZE, POINT_2D_STRUCT_TYPE ); packer.pack( point.srid() ); -// packer.pack( point.x(), point.y() ); + packer.pack( point.x() ); + packer.pack( point.y() ); } private void packPoint3D( Point3D point ) throws IOException { - packer.packStructHeader( POINT_STRUCT_SIZE, POINT_3D_STRUCT_TYPE ); + packer.packStructHeader( POINT_3D_STRUCT_SIZE, POINT_3D_STRUCT_TYPE ); packer.pack( point.srid() ); -// packer.pack( point.x(), point.y(), point.z() ); + packer.pack( point.x() ); + packer.pack( point.y() ); + packer.pack( point.z() ); } } @@ -107,12 +112,12 @@ Value unpackStruct( long size, byte type ) throws IOException { if ( type == POINT_2D_STRUCT_TYPE ) { - ensureCorrectStructSize( POINT_2D_TyCon.typeName(), POINT_STRUCT_SIZE, size ); + ensureCorrectStructSize( POINT_2D_TyCon.typeName(), POINT_2D_STRUCT_SIZE, size ); return unpackPoint2D(); } else if ( type == POINT_3D_STRUCT_TYPE ) { - ensureCorrectStructSize( POINT_3D_TyCon.typeName(), POINT_STRUCT_SIZE, size ); + ensureCorrectStructSize( POINT_3D_TyCon.typeName(), POINT_3D_STRUCT_SIZE, size ); return unpackPoint3D(); } else @@ -124,13 +129,18 @@ else if ( type == POINT_3D_STRUCT_TYPE ) private Value unpackPoint2D() throws IOException { long srid = unpacker.unpackLong(); - return new Point2DValue( new InternalPoint2D( srid, 0.0, 0.0 ) ); + double x = unpacker.unpackDouble(); + double y = unpacker.unpackDouble(); + return new Point2DValue( new InternalPoint2D( srid, x, y ) ); } private Value unpackPoint3D() throws IOException { long srid = unpacker.unpackLong(); - return new Point3DValue( new InternalPoint3D( srid, 0.0, 0.0, 0.0 ) ); + double x = unpacker.unpackDouble(); + double y = unpacker.unpackDouble(); + double z = unpacker.unpackDouble(); + return new Point3DValue( new InternalPoint3D( srid, x, y, z ) ); } } } diff --git a/driver/src/main/java/org/neo4j/driver/internal/packstream/PackInput.java b/driver/src/main/java/org/neo4j/driver/internal/packstream/PackInput.java index 87b306a959..6b6d3f7275 100644 --- a/driver/src/main/java/org/neo4j/driver/internal/packstream/PackInput.java +++ b/driver/src/main/java/org/neo4j/driver/internal/packstream/PackInput.java @@ -42,7 +42,7 @@ public interface PackInput double readDouble() throws IOException; /** Consume a specified number of bytes */ - PackInput readBytes( byte[] into, int offset, int toRead ) throws IOException; + void readBytes( byte[] into, int offset, int toRead ) throws IOException; /** Get the next byte without forwarding the internal pointer */ byte peekByte() throws IOException; diff --git a/driver/src/main/java/org/neo4j/driver/v1/Values.java b/driver/src/main/java/org/neo4j/driver/v1/Values.java index e52fc54a52..96572329fe 100644 --- a/driver/src/main/java/org/neo4j/driver/v1/Values.java +++ b/driver/src/main/java/org/neo4j/driver/v1/Values.java @@ -263,12 +263,12 @@ public static Value value( final Map val ) return new MapValue( asValues ); } - public static Value point2D( int srid, double x, double y ) + public static Value point2D( long srid, double x, double y ) { return new Point2DValue( new InternalPoint2D( srid, x, y ) ); } - public static Value point3D( int srid, double x, double y, double z ) + public static Value point3D( long srid, double x, double y, double z ) { return new Point3DValue( new InternalPoint3D( srid, x, y, z ) ); } diff --git a/driver/src/test/java/org/neo4j/driver/internal/packstream/BufferedChannelInput.java b/driver/src/test/java/org/neo4j/driver/internal/packstream/BufferedChannelInput.java index 9d27bfb763..e10120ad73 100644 --- a/driver/src/test/java/org/neo4j/driver/internal/packstream/BufferedChannelInput.java +++ b/driver/src/test/java/org/neo4j/driver/internal/packstream/BufferedChannelInput.java @@ -80,7 +80,7 @@ public double readDouble() throws IOException } @Override - public PackInput readBytes( byte[] into, int index, int toRead ) throws IOException + public void readBytes( byte[] into, int index, int toRead ) throws IOException { int endIndex = index + toRead; while ( index < endIndex) @@ -98,7 +98,6 @@ public PackInput readBytes( byte[] into, int index, int toRead ) throws IOExcept } } } - return this; } @Override diff --git a/driver/src/test/java/org/neo4j/driver/v1/integration/PointTypeIT.java b/driver/src/test/java/org/neo4j/driver/v1/integration/PointsIT.java similarity index 55% rename from driver/src/test/java/org/neo4j/driver/v1/integration/PointTypeIT.java rename to driver/src/test/java/org/neo4j/driver/v1/integration/PointsIT.java index 118d050c7e..873f30d84e 100644 --- a/driver/src/test/java/org/neo4j/driver/v1/integration/PointTypeIT.java +++ b/driver/src/test/java/org/neo4j/driver/v1/integration/PointsIT.java @@ -27,23 +27,20 @@ import org.neo4j.driver.v1.Record; import org.neo4j.driver.v1.Value; -import org.neo4j.driver.v1.types.Point; +import org.neo4j.driver.v1.types.Point2D; import org.neo4j.driver.v1.util.TestNeo4jSession; -import static java.util.Arrays.asList; import static java.util.Collections.singletonMap; import static org.junit.Assert.assertEquals; import static org.junit.Assume.assumeTrue; import static org.neo4j.driver.internal.util.ServerVersion.v3_4_0; -import static org.neo4j.driver.v1.Values.point; +import static org.neo4j.driver.v1.Values.point2D; -public class PointTypeIT +public class PointsIT { - private static final int EPSG_TABLE_ID = 1; - private static final int WGS_84_CRS_CODE = 4326; - - private static final int SR_ORG_TABLE_ID = 2; - private static final int CARTESIAN_CRS_CODE = 7203; + private static final long WGS_84_CRS_CODE = 4326; + private static final long CARTESIAN_CRS_CODE = 7203; + private static final double DELTA = 0.00001; @Rule public final TestNeo4jSession session = new TestNeo4jSession(); @@ -59,33 +56,33 @@ public void shouldReceivePoint() { Record record = session.run( "RETURN point({x: 39.111748, y:-76.775635})" ).single(); - Point point = record.get( 0 ).asPoint(); + Point2D point = record.get( 0 ).asPoint2D(); - assertEquals( SR_ORG_TABLE_ID, point.crsTableId() ); - assertEquals( CARTESIAN_CRS_CODE, point.crsCode() ); - assertEquals( asList( 39.111748, -76.775635 ), point.coordinate().values() ); + assertEquals( CARTESIAN_CRS_CODE, point.srid() ); + assertEquals( 39.111748, point.x(), DELTA ); + assertEquals( -76.775635, point.y(), DELTA ); } @Test public void shouldSendPoint() { - Value pointValue = point( EPSG_TABLE_ID, WGS_84_CRS_CODE, 38.8719, 77.0563 ); + Value pointValue = point2D( WGS_84_CRS_CODE, 38.8719, 77.0563 ); Record record1 = session.run( "CREATE (n:Node {location: $point}) RETURN 42", singletonMap( "point", pointValue ) ).single(); assertEquals( 42, record1.get( 0 ).asInt() ); Record record2 = session.run( "MATCH (n:Node) RETURN n.location" ).single(); - Point point = record2.get( 0 ).asPoint(); + Point2D point = record2.get( 0 ).asPoint2D(); - assertEquals( EPSG_TABLE_ID, point.crsTableId() ); - assertEquals( WGS_84_CRS_CODE, point.crsCode() ); - assertEquals( asList( 38.8719, 77.0563 ), point.coordinate().values() ); + assertEquals( WGS_84_CRS_CODE, point.srid() ); + assertEquals( 38.8719, point.x(), DELTA ); + assertEquals( 77.0563, point.y(), DELTA ); } @Test public void shouldSendAndReceivePoint() { - testPointSendAndReceive( SR_ORG_TABLE_ID, CARTESIAN_CRS_CODE, 40.7624, 73.9738 ); + testPointSendAndReceive( point2D( CARTESIAN_CRS_CODE, 40.7624, 73.9738 ) ); } @Test @@ -94,34 +91,27 @@ public void shouldSendAndReceiveRandomPoints() Stream randomPoints = ThreadLocalRandom.current() .ints( 1_000, 0, 2 ) .mapToObj( idx -> idx % 2 == 0 - ? point( EPSG_TABLE_ID, WGS_84_CRS_CODE, randomCoordinate() ) - : point( SR_ORG_TABLE_ID, CARTESIAN_CRS_CODE, randomCoordinate() ) ); + ? point2D( WGS_84_CRS_CODE, randomDouble(), randomDouble() ) + : point2D( CARTESIAN_CRS_CODE, randomDouble(), randomDouble() ) ); randomPoints.forEach( this::testPointSendAndReceive ); } - private void testPointSendAndReceive( long crsTableId, long crsCode, double... coordinate ) - { - testPointSendAndReceive( point( crsTableId, crsCode, coordinate ) ); - } - private void testPointSendAndReceive( Value pointValue ) { - Point originalPoint = pointValue.asPoint(); + Point2D originalPoint = pointValue.asPoint2D(); Record record = session.run( "CREATE (n {p:$point}) return n.p", singletonMap( "point", pointValue ) ).single(); - Point receivedPoint = record.get( 0 ).asPoint(); + Point2D receivedPoint = record.get( 0 ).asPoint2D(); String message = "Failed for " + originalPoint; - assertEquals( message, originalPoint.crsTableId(), receivedPoint.crsTableId() ); - assertEquals( message, originalPoint.crsCode(), receivedPoint.crsCode() ); - assertEquals( message, originalPoint.coordinate().values(), receivedPoint.coordinate().values() ); + assertEquals( message, originalPoint.srid(), receivedPoint.srid() ); + assertEquals( message, originalPoint.x(), receivedPoint.x(), DELTA ); + assertEquals( message, originalPoint.y(), receivedPoint.y(), DELTA ); } - private static double[] randomCoordinate() + private static double randomDouble() { - ThreadLocalRandom random = ThreadLocalRandom.current(); - int count = random.nextInt( 2, 4 ); // either 2D or 3D point - return random.doubles( count, -180.0, 180 ).toArray(); + return ThreadLocalRandom.current().nextDouble( -180.0, 180 ); } } From c522cdcb87644ff9a2022c6b4513221012e2efe5 Mon Sep 17 00:00:00 2001 From: lutovich Date: Tue, 20 Feb 2018 13:45:33 +0100 Subject: [PATCH 5/7] Added couple unit tests for points in pack stream To assert that both 2D and 3D points can be serialized and deserialized. --- .../driver/internal/InternalPoint2D.java | 25 ++++ .../driver/internal/InternalPoint3D.java | 26 ++++ .../PackStreamMessageFormatV2Test.java | 122 ++++++++++++++++++ .../driver/internal/util/ByteBufOutput.java | 75 +++++++++++ .../internal/util/MessageToByteBufWriter.java | 53 -------- .../internal/util/ThrowingConsumer.java | 25 ++++ 6 files changed, 273 insertions(+), 53 deletions(-) create mode 100644 driver/src/test/java/org/neo4j/driver/internal/util/ByteBufOutput.java create mode 100644 driver/src/test/java/org/neo4j/driver/internal/util/ThrowingConsumer.java diff --git a/driver/src/main/java/org/neo4j/driver/internal/InternalPoint2D.java b/driver/src/main/java/org/neo4j/driver/internal/InternalPoint2D.java index 8b236a0f5f..9cbde4cebd 100644 --- a/driver/src/main/java/org/neo4j/driver/internal/InternalPoint2D.java +++ b/driver/src/main/java/org/neo4j/driver/internal/InternalPoint2D.java @@ -18,6 +18,8 @@ */ package org.neo4j.driver.internal; +import java.util.Objects; + import org.neo4j.driver.v1.types.Point2D; public class InternalPoint2D implements Point2D @@ -50,4 +52,27 @@ public double y() { return y; } + + @Override + public boolean equals( Object o ) + { + if ( this == o ) + { + return true; + } + if ( o == null || getClass() != o.getClass() ) + { + return false; + } + InternalPoint2D that = (InternalPoint2D) o; + return srid == that.srid && + Double.compare( that.x, x ) == 0 && + Double.compare( that.y, y ) == 0; + } + + @Override + public int hashCode() + { + return Objects.hash( srid, x, y ); + } } diff --git a/driver/src/main/java/org/neo4j/driver/internal/InternalPoint3D.java b/driver/src/main/java/org/neo4j/driver/internal/InternalPoint3D.java index a53cce8396..5de045c99c 100644 --- a/driver/src/main/java/org/neo4j/driver/internal/InternalPoint3D.java +++ b/driver/src/main/java/org/neo4j/driver/internal/InternalPoint3D.java @@ -18,6 +18,8 @@ */ package org.neo4j.driver.internal; +import java.util.Objects; + import org.neo4j.driver.v1.types.Point3D; public class InternalPoint3D implements Point3D @@ -58,4 +60,28 @@ public double z() { return z; } + + @Override + public boolean equals( Object o ) + { + if ( this == o ) + { + return true; + } + if ( o == null || getClass() != o.getClass() ) + { + return false; + } + InternalPoint3D that = (InternalPoint3D) o; + return srid == that.srid && + Double.compare( that.x, x ) == 0 && + Double.compare( that.y, y ) == 0 && + Double.compare( that.z, z ) == 0; + } + + @Override + public int hashCode() + { + return Objects.hash( srid, x, y, z ); + } } diff --git a/driver/src/test/java/org/neo4j/driver/internal/messaging/PackStreamMessageFormatV2Test.java b/driver/src/test/java/org/neo4j/driver/internal/messaging/PackStreamMessageFormatV2Test.java index 8b76bdecaa..80e4d9b6bf 100644 --- a/driver/src/test/java/org/neo4j/driver/internal/messaging/PackStreamMessageFormatV2Test.java +++ b/driver/src/test/java/org/neo4j/driver/internal/messaging/PackStreamMessageFormatV2Test.java @@ -18,12 +18,35 @@ */ package org.neo4j.driver.internal.messaging; +import io.netty.buffer.ByteBuf; +import io.netty.buffer.Unpooled; import org.junit.Test; +import java.io.IOException; +import java.util.ArrayList; +import java.util.Collections; +import java.util.List; + +import org.neo4j.driver.internal.InternalPoint2D; +import org.neo4j.driver.internal.InternalPoint3D; +import org.neo4j.driver.internal.async.inbound.ByteBufInput; import org.neo4j.driver.internal.packstream.PackOutput; +import org.neo4j.driver.internal.util.ByteBufOutput; +import org.neo4j.driver.internal.util.ThrowingConsumer; +import org.neo4j.driver.v1.Value; +import static java.util.Collections.singletonMap; +import static org.junit.Assert.assertEquals; import static org.junit.Assert.fail; +import static org.mockito.Matchers.any; +import static org.mockito.Mockito.doAnswer; import static org.mockito.Mockito.mock; +import static org.neo4j.driver.internal.messaging.PackStreamMessageFormatV1.MSG_RECORD; +import static org.neo4j.driver.internal.packstream.PackStream.FLOAT_64; +import static org.neo4j.driver.internal.packstream.PackStream.Packer; +import static org.neo4j.driver.v1.Values.point2D; +import static org.neo4j.driver.v1.Values.point3D; +import static org.neo4j.driver.v1.util.TestUtil.assertByteBufContains; public class PackStreamMessageFormatV2Test { @@ -43,4 +66,103 @@ public void shouldFailToCreateWriterWithoutByteArraySupport() { } } + + @Test + public void shouldWritePoint2D() throws Exception + { + ByteBuf buf = Unpooled.buffer(); + MessageFormat.Writer writer = messageFormat.newWriter( new ByteBufOutput( buf ), true ); + + writer.write( new RunMessage( "RETURN $point", singletonMap( "point", point2D( 42, 12.99, -180.0 ) ) ) ); + + // point should be encoded as (byte SRID, FLOAT_64 header byte + double X, FLOAT_64 header byte + double Y) + int index = buf.readableBytes() - Double.BYTES - Byte.BYTES - Double.BYTES - Byte.BYTES - Byte.BYTES; + ByteBuf tailSlice = buf.slice( index, buf.readableBytes() - index ); + + assertByteBufContains( tailSlice, (byte) 42, FLOAT_64, 12.99, FLOAT_64, -180.0 ); + } + + @Test + public void shouldWritePoint3D() throws Exception + { + ByteBuf buf = Unpooled.buffer(); + MessageFormat.Writer writer = messageFormat.newWriter( new ByteBufOutput( buf ), true ); + + writer.write( new RunMessage( "RETURN $point", singletonMap( "point", point3D( 42, 0.51, 2.99, 100.123 ) ) ) ); + + // point should be encoded as (byte SRID, FLOAT_64 header byte + double X, FLOAT_64 header byte + double Y, FLOAT_64 header byte + double Z) + int index = buf.readableBytes() - Double.BYTES - Byte.BYTES - Double.BYTES - Byte.BYTES - Double.BYTES - Byte.BYTES - Byte.BYTES; + ByteBuf tailSlice = buf.slice( index, buf.readableBytes() - index ); + + assertByteBufContains( tailSlice, (byte) 42, FLOAT_64, 0.51, FLOAT_64, 2.99, FLOAT_64, 100.123 ); + } + + @Test + public void shouldReadPoint2D() throws Exception + { + InternalPoint2D point = new InternalPoint2D( 42, 120.65, -99.2 ); + + testReadingOfPoint( packer -> + { + packer.packStructHeader( 3, (byte) 'X' ); + packer.pack( point.srid() ); + packer.pack( point.x() ); + packer.pack( point.y() ); + }, point ); + } + + @Test + public void shouldReadPoint3D() throws Exception + { + InternalPoint3D point = new InternalPoint3D( 42, 85.391, 98.8, 11.1 ); + + testReadingOfPoint( packer -> + { + packer.packStructHeader( 4, (byte) 'Y' ); + packer.pack( point.srid() ); + packer.pack( point.x() ); + packer.pack( point.y() ); + packer.pack( point.z() ); + }, point ); + } + + private void testReadingOfPoint( ThrowingConsumer packAction, Object expected ) throws Exception + { + ByteBuf buf = Unpooled.buffer(); + try + { + Packer packer = new Packer( new ByteBufOutput( buf ) ); + packer.packStructHeader( 1, MSG_RECORD ); + packer.packListHeader( 1 ); + packAction.accept( packer ); + + ByteBufInput input = new ByteBufInput(); + input.start( buf ); + MessageFormat.Reader reader = messageFormat.newReader( input ); + + List values = new ArrayList<>(); + MessageHandler messageHandler = recordMemorizingHandler( values ); + reader.read( messageHandler ); + input.stop(); + + assertEquals( 1, values.size() ); + assertEquals( expected, values.get( 0 ).asObject() ); + } + finally + { + buf.release(); + } + } + + private static MessageHandler recordMemorizingHandler( List values ) throws IOException + { + MessageHandler messageHandler = mock( MessageHandler.class ); + doAnswer( invocation -> + { + Value[] arg = invocation.getArgumentAt( 0, Value[].class ); + Collections.addAll( values, arg ); + return null; + } ).when( messageHandler ).handleRecordMessage( any() ); + return messageHandler; + } } diff --git a/driver/src/test/java/org/neo4j/driver/internal/util/ByteBufOutput.java b/driver/src/test/java/org/neo4j/driver/internal/util/ByteBufOutput.java new file mode 100644 index 0000000000..988589c04f --- /dev/null +++ b/driver/src/test/java/org/neo4j/driver/internal/util/ByteBufOutput.java @@ -0,0 +1,75 @@ +/* + * Copyright (c) 2002-2018 "Neo Technology," + * Network Engine for Objects in Lund AB [http://neotechnology.com] + * + * This file is part of Neo4j. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.neo4j.driver.internal.util; + +import io.netty.buffer.ByteBuf; + +import org.neo4j.driver.internal.packstream.PackOutput; + +public class ByteBufOutput implements PackOutput +{ + private final ByteBuf buf; + + public ByteBufOutput( ByteBuf buf ) + { + this.buf = buf; + } + + @Override + public PackOutput writeByte( byte value ) + { + buf.writeByte( value ); + return this; + } + + @Override + public PackOutput writeBytes( byte[] data ) + { + buf.writeBytes( data ); + return this; + } + + @Override + public PackOutput writeShort( short value ) + { + buf.writeShort( value ); + return this; + } + + @Override + public PackOutput writeInt( int value ) + { + buf.writeInt( value ); + return this; + } + + @Override + public PackOutput writeLong( long value ) + { + buf.writeLong( value ); + return this; + } + + @Override + public PackOutput writeDouble( double value ) + { + buf.writeDouble( value ); + return this; + } +} diff --git a/driver/src/test/java/org/neo4j/driver/internal/util/MessageToByteBufWriter.java b/driver/src/test/java/org/neo4j/driver/internal/util/MessageToByteBufWriter.java index c74e7b673c..a1d5cb269c 100644 --- a/driver/src/test/java/org/neo4j/driver/internal/util/MessageToByteBufWriter.java +++ b/driver/src/test/java/org/neo4j/driver/internal/util/MessageToByteBufWriter.java @@ -25,7 +25,6 @@ import org.neo4j.driver.internal.messaging.Message; import org.neo4j.driver.internal.messaging.MessageFormat; -import org.neo4j.driver.internal.packstream.PackOutput; public class MessageToByteBufWriter { @@ -50,56 +49,4 @@ public ByteBuf asByteBuf( Message message ) throw new RuntimeException( e ); } } - - private static class ByteBufOutput implements PackOutput - { - final ByteBuf buf; - - ByteBufOutput( ByteBuf buf ) - { - this.buf = buf; - } - - @Override - public PackOutput writeByte( byte value ) - { - buf.writeByte( value ); - return this; - } - - @Override - public PackOutput writeBytes( byte[] data ) - { - buf.writeBytes( data ); - return this; - } - - @Override - public PackOutput writeShort( short value ) - { - buf.writeShort( value ); - return this; - } - - @Override - public PackOutput writeInt( int value ) - { - buf.writeInt( value ); - return this; - } - - @Override - public PackOutput writeLong( long value ) - { - buf.writeLong( value ); - return this; - } - - @Override - public PackOutput writeDouble( double value ) - { - buf.writeDouble( value ); - return this; - } - } } diff --git a/driver/src/test/java/org/neo4j/driver/internal/util/ThrowingConsumer.java b/driver/src/test/java/org/neo4j/driver/internal/util/ThrowingConsumer.java new file mode 100644 index 0000000000..2e1a332a0d --- /dev/null +++ b/driver/src/test/java/org/neo4j/driver/internal/util/ThrowingConsumer.java @@ -0,0 +1,25 @@ +/* + * Copyright (c) 2002-2018 "Neo Technology," + * Network Engine for Objects in Lund AB [http://neotechnology.com] + * + * This file is part of Neo4j. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.neo4j.driver.internal.util; + +@FunctionalInterface +public interface ThrowingConsumer +{ + void accept( T t ) throws Exception; +} From d0c52f674c5398cad39762f7066f55671a23136e Mon Sep 17 00:00:00 2001 From: lutovich Date: Wed, 21 Feb 2018 15:03:57 +0100 Subject: [PATCH 6/7] Added integration test for 2D point arrays --- .../neo4j/driver/v1/integration/PointsIT.java | 64 +++++++++++++++---- 1 file changed, 52 insertions(+), 12 deletions(-) diff --git a/driver/src/test/java/org/neo4j/driver/v1/integration/PointsIT.java b/driver/src/test/java/org/neo4j/driver/v1/integration/PointsIT.java index 873f30d84e..c6e92a52a8 100644 --- a/driver/src/test/java/org/neo4j/driver/v1/integration/PointsIT.java +++ b/driver/src/test/java/org/neo4j/driver/v1/integration/PointsIT.java @@ -22,7 +22,9 @@ import org.junit.Rule; import org.junit.Test; +import java.util.List; import java.util.concurrent.ThreadLocalRandom; +import java.util.stream.IntStream; import java.util.stream.Stream; import org.neo4j.driver.v1.Record; @@ -31,9 +33,11 @@ import org.neo4j.driver.v1.util.TestNeo4jSession; import static java.util.Collections.singletonMap; +import static java.util.stream.Collectors.toList; import static org.junit.Assert.assertEquals; import static org.junit.Assume.assumeTrue; import static org.neo4j.driver.internal.util.ServerVersion.v3_4_0; +import static org.neo4j.driver.v1.Values.ofPoint2D; import static org.neo4j.driver.v1.Values.point2D; public class PointsIT @@ -52,7 +56,7 @@ public void setUp() } @Test - public void shouldReceivePoint() + public void shouldReceivePoint2D() { Record record = session.run( "RETURN point({x: 39.111748, y:-76.775635})" ).single(); @@ -64,7 +68,7 @@ public void shouldReceivePoint() } @Test - public void shouldSendPoint() + public void shouldSendPoint2D() { Value pointValue = point2D( WGS_84_CRS_CODE, 38.8719, 77.0563 ); Record record1 = session.run( "CREATE (n:Node {location: $point}) RETURN 42", singletonMap( "point", pointValue ) ).single(); @@ -80,13 +84,13 @@ public void shouldSendPoint() } @Test - public void shouldSendAndReceivePoint() + public void shouldSendAndReceivePoint2D() { - testPointSendAndReceive( point2D( CARTESIAN_CRS_CODE, 40.7624, 73.9738 ) ); + testPoint2DSendAndReceive( point2D( CARTESIAN_CRS_CODE, 40.7624, 73.9738 ) ); } @Test - public void shouldSendAndReceiveRandomPoints() + public void shouldSendAndReceiveRandom2DPoints() { Stream randomPoints = ThreadLocalRandom.current() .ints( 1_000, 0, 2 ) @@ -94,24 +98,60 @@ public void shouldSendAndReceiveRandomPoints() ? point2D( WGS_84_CRS_CODE, randomDouble(), randomDouble() ) : point2D( CARTESIAN_CRS_CODE, randomDouble(), randomDouble() ) ); - randomPoints.forEach( this::testPointSendAndReceive ); + randomPoints.forEach( this::testPoint2DSendAndReceive ); } - private void testPointSendAndReceive( Value pointValue ) + @Test + public void shouldSendAndReceiveRandom2DPointArrays() + { + Stream> randomPointLists = ThreadLocalRandom.current() + .ints( 1_000, 0, 2 ) + .mapToObj( PointsIT::randomPoint2DList ); + + randomPointLists.forEach( this::testPoint2DListSendAndReceive ); + } + + private void testPoint2DSendAndReceive( Value pointValue ) { Point2D originalPoint = pointValue.asPoint2D(); - Record record = session.run( "CREATE (n {p:$point}) return n.p", singletonMap( "point", pointValue ) ).single(); + Record record = session.run( "CREATE (n {point: $point}) return n.point", singletonMap( "point", pointValue ) ).single(); Point2D receivedPoint = record.get( 0 ).asPoint2D(); - String message = "Failed for " + originalPoint; - assertEquals( message, originalPoint.srid(), receivedPoint.srid() ); - assertEquals( message, originalPoint.x(), receivedPoint.x(), DELTA ); - assertEquals( message, originalPoint.y(), receivedPoint.y(), DELTA ); + assertPoints2DEqual( originalPoint, receivedPoint ); + } + + private void testPoint2DListSendAndReceive( List points ) + { + Record record = session.run( "CREATE (n {points: $points}) return n.points", singletonMap( "points", points ) ).single(); + List receivedPoints = record.get( 0 ).asList( ofPoint2D() ); + + assertEquals( points.size(), receivedPoints.size() ); + for ( int i = 0; i < points.size(); i++ ) + { + assertPoints2DEqual( points.get( i ).asPoint2D(), receivedPoints.get( i ) ); + } + } + + private static List randomPoint2DList( int index ) + { + int size = ThreadLocalRandom.current().nextInt( 1, 100 ); + long srid = index % 2 == 0 ? CARTESIAN_CRS_CODE : WGS_84_CRS_CODE; + return IntStream.range( 0, size ) + .mapToObj( i -> point2D( srid, randomDouble(), randomDouble() ) ) + .collect( toList() ); } private static double randomDouble() { return ThreadLocalRandom.current().nextDouble( -180.0, 180 ); } + + private static void assertPoints2DEqual( Point2D expected, Point2D actual ) + { + String message = "Expected: " + expected + " but was: " + actual; + assertEquals( message, expected.srid(), actual.srid() ); + assertEquals( message, expected.x(), actual.x(), DELTA ); + assertEquals( message, expected.y(), actual.y(), DELTA ); + } } From 454ed6f1d256fdb3b257a2d4f0d6bf1f1e7c2d37 Mon Sep 17 00:00:00 2001 From: lutovich Date: Wed, 21 Feb 2018 15:05:01 +0100 Subject: [PATCH 7/7] Added toString() to 2D and 3D point implementations --- .../org/neo4j/driver/internal/InternalPoint2D.java | 10 ++++++++++ .../org/neo4j/driver/internal/InternalPoint3D.java | 11 +++++++++++ 2 files changed, 21 insertions(+) diff --git a/driver/src/main/java/org/neo4j/driver/internal/InternalPoint2D.java b/driver/src/main/java/org/neo4j/driver/internal/InternalPoint2D.java index 9cbde4cebd..9e4d654acb 100644 --- a/driver/src/main/java/org/neo4j/driver/internal/InternalPoint2D.java +++ b/driver/src/main/java/org/neo4j/driver/internal/InternalPoint2D.java @@ -75,4 +75,14 @@ public int hashCode() { return Objects.hash( srid, x, y ); } + + @Override + public String toString() + { + return "Point2D{" + + "srid=" + srid + + ", x=" + x + + ", y=" + y + + '}'; + } } diff --git a/driver/src/main/java/org/neo4j/driver/internal/InternalPoint3D.java b/driver/src/main/java/org/neo4j/driver/internal/InternalPoint3D.java index 5de045c99c..96fdd0d005 100644 --- a/driver/src/main/java/org/neo4j/driver/internal/InternalPoint3D.java +++ b/driver/src/main/java/org/neo4j/driver/internal/InternalPoint3D.java @@ -84,4 +84,15 @@ public int hashCode() { return Objects.hash( srid, x, y, z ); } + + @Override + public String toString() + { + return "Point3D{" + + "srid=" + srid + + ", x=" + x + + ", y=" + y + + ", z=" + z + + '}'; + } }