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 313059df95..419b75b0a6 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 @@ -42,7 +42,7 @@ interface Reader } - Writer newWriter( WritableByteChannel ch ); + Writer newWriter( WritableByteChannel ch, boolean byteArraySupportEnabled ); Reader newReader( ReadableByteChannel ch ); 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 14dfb36432..29df271b00 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 @@ -37,6 +37,7 @@ import org.neo4j.driver.internal.packstream.PackOutput; import org.neo4j.driver.internal.packstream.PackStream; import org.neo4j.driver.internal.packstream.PackType; +import org.neo4j.driver.internal.packstream.ByteArrayIncompatiblePacker; import org.neo4j.driver.internal.util.Iterables; import org.neo4j.driver.internal.value.InternalValue; import org.neo4j.driver.internal.value.ListValue; @@ -79,10 +80,10 @@ public class PackStreamMessageFormatV1 implements MessageFormat private static final Map EMPTY_STRING_VALUE_MAP = new HashMap<>( 0 ); @Override - public MessageFormat.Writer newWriter( WritableByteChannel ch ) + public MessageFormat.Writer newWriter( WritableByteChannel ch, boolean byteArraySupportEnabled ) { ChunkedOutput output = new ChunkedOutput( ch ); - return new Writer( output, output.messageBoundaryHook() ); + return new Writer( output, output.messageBoundaryHook(), byteArraySupportEnabled ); } @Override @@ -106,11 +107,19 @@ public static class Writer implements MessageFormat.Writer, MessageHandler /** * @param output interface to write messages to * @param onMessageComplete invoked for each message, after it's done writing to the output + * @param byteArraySupportEnabled specify if support to pack/write byte array to server */ - public Writer( PackOutput output, Runnable onMessageComplete ) + public Writer( PackOutput output, Runnable onMessageComplete, boolean byteArraySupportEnabled ) { this.onMessageComplete = onMessageComplete; - packer = new PackStream.Packer( output ); + if( byteArraySupportEnabled ) + { + packer = new PackStream.Packer( output ); + } + else + { + packer = new ByteArrayIncompatiblePacker( output ); + } } @Override @@ -223,6 +232,10 @@ private void packValue( Value value ) throws IOException packer.packNull(); break; + case BYTES_TyCon: + packer.pack( value.asByteArray() ); + break; + case STRING_TyCon: packer.pack( value.asString() ); break; @@ -502,8 +515,6 @@ private Value unpackValue() throws IOException PackType type = unpacker.peekNextType(); switch ( type ) { - case BYTES: - break; case NULL: return value( unpacker.unpackNull() ); case BOOLEAN: @@ -512,6 +523,8 @@ private Value unpackValue() throws IOException return value( unpacker.unpackLong() ); case FLOAT: return value( unpacker.unpackDouble() ); + case BYTES: + return value( unpacker.unpackBytes() ); case STRING: return value( unpacker.unpackString() ); case MAP: diff --git a/driver/src/main/java/org/neo4j/driver/internal/net/ChunkedOutput.java b/driver/src/main/java/org/neo4j/driver/internal/net/ChunkedOutput.java index 8ec147cc0b..505b152c93 100644 --- a/driver/src/main/java/org/neo4j/driver/internal/net/ChunkedOutput.java +++ b/driver/src/main/java/org/neo4j/driver/internal/net/ChunkedOutput.java @@ -40,7 +40,6 @@ public class ChunkedOutput implements PackOutput /** Are currently in the middle of writing a chunk? */ private boolean chunkOpen = false; - public ChunkedOutput( WritableByteChannel ch ) { this( 8192, ch ); diff --git a/driver/src/main/java/org/neo4j/driver/internal/net/SocketClient.java b/driver/src/main/java/org/neo4j/driver/internal/net/SocketClient.java index 9ca84c56a2..aff448a556 100644 --- a/driver/src/main/java/org/neo4j/driver/internal/net/SocketClient.java +++ b/driver/src/main/java/org/neo4j/driver/internal/net/SocketClient.java @@ -34,6 +34,8 @@ import static java.lang.String.format; import static java.nio.ByteOrder.BIG_ENDIAN; +import static org.neo4j.driver.internal.util.ServerVersion.v3_2_0; +import static org.neo4j.driver.internal.util.ServerVersion.version; public class SocketClient { @@ -123,9 +125,7 @@ public void start() { setChannel( ChannelFactory.create( address, securityPlan, timeoutMillis, logger ) ); } - protocol = negotiateProtocol(); - reader = protocol.reader(); - writer = protocol.writer(); + setProtocol( negotiateProtocol() ); } catch ( ConnectException e ) { @@ -139,6 +139,21 @@ public void start() } } + public void updateProtocol( String serverVersion ) + { + if( version( serverVersion ).lessThan( v3_2_0 ) ) + { + setProtocol( SocketProtocolV1.createWithoutByteArraySupport( channel ) ); + } + } + + private void setProtocol( SocketProtocol protocol ) + { + this.protocol = protocol; + this.reader = protocol.reader(); + this.writer = protocol.writer(); + } + public void send( Queue messages ) throws IOException { int messageCount = 0; @@ -255,7 +270,7 @@ private SocketProtocol negotiateProtocol() throws IOException { case VERSION1: logger.debug( "S: [HANDSHAKE] -> 1" ); - return new SocketProtocolV1( channel ); + return SocketProtocolV1.create( channel ); case NO_VERSION: throw new ClientException( "The server does not support any of the protocol versions supported by " + "this driver. Ensure that you are using driver and server versions that " + diff --git a/driver/src/main/java/org/neo4j/driver/internal/net/SocketConnection.java b/driver/src/main/java/org/neo4j/driver/internal/net/SocketConnection.java index 4d25c48a20..64d5356053 100644 --- a/driver/src/main/java/org/neo4j/driver/internal/net/SocketConnection.java +++ b/driver/src/main/java/org/neo4j/driver/internal/net/SocketConnection.java @@ -117,6 +117,7 @@ public void init( String clientName, Map authToken ) queueMessage( new InitMessage( clientName, authToken ), initCollector ); sync(); this.serverInfo = new InternalServerInfo( socket.address(), initCollector.serverVersion() ); + socket.updateProtocol( serverInfo.version() ); } @Override @@ -167,6 +168,7 @@ public synchronized void flush() } catch ( IOException e ) { + close(); throw new ServiceUnavailableException( "Unable to send messages to server: " + e.getMessage(), e ); } } diff --git a/driver/src/main/java/org/neo4j/driver/internal/net/SocketProtocolV1.java b/driver/src/main/java/org/neo4j/driver/internal/net/SocketProtocolV1.java index b98a3ce83b..1b104e9b05 100644 --- a/driver/src/main/java/org/neo4j/driver/internal/net/SocketProtocolV1.java +++ b/driver/src/main/java/org/neo4j/driver/internal/net/SocketProtocolV1.java @@ -18,7 +18,6 @@ */ package org.neo4j.driver.internal.net; -import java.io.IOException; import java.nio.channels.ByteChannel; import org.neo4j.driver.internal.messaging.MessageFormat; @@ -32,15 +31,22 @@ public class SocketProtocolV1 implements SocketProtocol private final Reader reader; private final Writer writer; - public SocketProtocolV1( ByteChannel channel ) throws IOException + public static SocketProtocol create( ByteChannel channel ) { - messageFormat = new PackStreamMessageFormatV1(); + /*by default the byte array support is enabled*/ + return new SocketProtocolV1( channel, true ); + } - ChunkedOutput output = new ChunkedOutput( channel ); - BufferingChunkedInput input = new BufferingChunkedInput( channel ); + public static SocketProtocol createWithoutByteArraySupport( ByteChannel channel ) + { + return new SocketProtocolV1( channel, false ); + } - this.writer = new PackStreamMessageFormatV1.Writer( output, output.messageBoundaryHook() ); - this.reader = new PackStreamMessageFormatV1.Reader( input, input.messageBoundaryHook() ); + private SocketProtocolV1( ByteChannel channel, boolean byteArraySupportEnabled ) + { + messageFormat = new PackStreamMessageFormatV1(); + this.writer = messageFormat.newWriter( channel, byteArraySupportEnabled ); + this.reader = messageFormat.newReader( channel ); } @Override 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 new file mode 100644 index 0000000000..639bd41b84 --- /dev/null +++ b/driver/src/main/java/org/neo4j/driver/internal/packstream/ByteArrayIncompatiblePacker.java @@ -0,0 +1,35 @@ +/* + * Copyright (c) 2002-2017 "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.packstream; + +import java.io.IOException; + +public class ByteArrayIncompatiblePacker extends PackStream.Packer +{ + public ByteArrayIncompatiblePacker( PackOutput out ) + { + super( out ); + } + + 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." ); + } +} 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 dbb12281ae..2852f94ec6 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 @@ -501,39 +501,38 @@ public double unpackDouble() throws IOException throw new Unexpected( "Expected a double, but got: " + toHexString( markerByte )); } - public String unpackString() throws IOException + public byte[] unpackBytes() throws IOException { final byte markerByte = in.readByte(); - if( markerByte == TINY_STRING ) // Note no mask, so we compare to 0x80. + switch(markerByte) { - return EMPTY_STRING; + case BYTES_8: return unpackRawBytes( unpackUINT8() ); + case BYTES_16: return unpackRawBytes( unpackUINT16() ); + case BYTES_32: + { + long size = unpackUINT32(); + if ( size <= Integer.MAX_VALUE ) + { + return unpackRawBytes( (int) size ); + } + else + { + throw new Overflow( "BYTES_32 too long for Java" ); + } + } + default: throw new Unexpected( "Expected bytes, but got: 0x" + toHexString( markerByte & 0xFF )); } - - return new String(unpackUtf8(markerByte), UTF_8); } - public byte[] unpackBytes() throws IOException + public String unpackString() throws IOException { final byte markerByte = in.readByte(); - - switch(markerByte) + if( markerByte == TINY_STRING ) // Note no mask, so we compare to 0x80. { - case BYTES_8: return unpackBytes( unpackUINT8() ); - case BYTES_16: return unpackBytes( unpackUINT16() ); - case BYTES_32: - { - long size = unpackUINT32(); - if ( size <= Integer.MAX_VALUE ) - { - return unpackBytes( (int) size ); - } - else - { - throw new Overflow( "BYTES_32 too long for Java" ); - } - } - default: throw new Unexpected( "Expected binary data, but got: 0x" + toHexString( markerByte & 0xFF )); + return EMPTY_STRING; } + + return new String(unpackUtf8(markerByte), UTF_8); } /** @@ -558,17 +557,17 @@ private byte[] unpackUtf8(byte markerByte) throws IOException final byte markerHighNibble = (byte) (markerByte & 0xF0); final byte markerLowNibble = (byte) (markerByte & 0x0F); - if ( markerHighNibble == TINY_STRING ) { return unpackBytes( markerLowNibble ); } + if ( markerHighNibble == TINY_STRING ) { return unpackRawBytes( markerLowNibble ); } switch(markerByte) { - case STRING_8: return unpackBytes( unpackUINT8() ); - case STRING_16: return unpackBytes( unpackUINT16() ); + case STRING_8: return unpackRawBytes( unpackUINT8() ); + case STRING_16: return unpackRawBytes( unpackUINT16() ); case STRING_32: { long size = unpackUINT32(); if ( size <= Integer.MAX_VALUE ) { - return unpackBytes( (int) size ); + return unpackRawBytes( (int) size ); } else { @@ -608,7 +607,7 @@ private long unpackUINT32() throws IOException return in.readInt() & 0xFFFFFFFFL; } - private byte[] unpackBytes( int size ) throws IOException + private byte[] unpackRawBytes(int size ) throws IOException { byte[] heapBuffer = new byte[size]; in.readBytes( heapBuffer, 0, heapBuffer.length ); @@ -711,5 +710,4 @@ public UnPackable( String message ) super( message ); } } - } diff --git a/driver/src/main/java/org/neo4j/driver/internal/summary/InternalServerInfo.java b/driver/src/main/java/org/neo4j/driver/internal/summary/InternalServerInfo.java index a965ffce65..a03e5efac9 100644 --- a/driver/src/main/java/org/neo4j/driver/internal/summary/InternalServerInfo.java +++ b/driver/src/main/java/org/neo4j/driver/internal/summary/InternalServerInfo.java @@ -16,6 +16,7 @@ * See the License for the specific language governing permissions and * limitations under the License. */ + package org.neo4j.driver.internal.summary; import org.neo4j.driver.internal.net.BoltServerAddress; 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 e1fc150f1b..dae25fcb83 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,6 +33,7 @@ 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.RELATIONSHIP_TyCon; +import static org.neo4j.driver.internal.types.TypeConstructor.BYTES_TyCon; import static org.neo4j.driver.internal.types.TypeConstructor.STRING_TyCon; /** @@ -47,6 +48,7 @@ public class InternalTypeSystem implements TypeSystem private final TypeRepresentation anyType = constructType( ANY_TyCon ); private final TypeRepresentation booleanType = constructType( BOOLEAN_TyCon ); + private final TypeRepresentation bytesType = constructType( BYTES_TyCon ); private final TypeRepresentation stringType = constructType( STRING_TyCon ); private final TypeRepresentation numberType = constructType( NUMBER_TyCon ); private final TypeRepresentation integerType = constructType( INTEGER_TyCon ); @@ -76,6 +78,13 @@ public Type BOOLEAN() return booleanType; } + /** the Cypher type BYTES */ + @Override + public Type BYTES() + { + return bytesType; + } + /** the Cypher type STRING */ @Override public Type STRING() 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 2805f0ef1e..c84f39add4 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 @@ -44,6 +44,14 @@ public String typeName() } }, + BYTES_TyCon { + @Override + public String typeName() + { + return "BYTES"; + } + }, + STRING_TyCon { @Override public String typeName() diff --git a/driver/src/main/java/org/neo4j/driver/internal/value/BytesValue.java b/driver/src/main/java/org/neo4j/driver/internal/value/BytesValue.java new file mode 100644 index 0000000000..c11fe254ec --- /dev/null +++ b/driver/src/main/java/org/neo4j/driver/internal/value/BytesValue.java @@ -0,0 +1,108 @@ +/* + * Copyright (c) 2002-2017 "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.Type; + +import java.util.Arrays; + +public class BytesValue extends ValueAdapter +{ + private final byte[] val; + + public BytesValue( byte[] val ) + { + if ( val == null ) + { + throw new IllegalArgumentException( "Cannot construct BytesValue from null" ); + } + this.val = val; + } + + @Override + public boolean isEmpty() + { + return val.length == 0; + } + + @Override + public int size() + { + return val.length; + } + + @Override + public byte[] asObject() + { + return val; + } + + @Override + public byte[] asByteArray() + { + return val; + } + + @Override + public Type type() + { + return InternalTypeSystem.TYPE_SYSTEM.BYTES(); + } + + @Override + public boolean equals( Object o ) + { + if ( this == o ) + { + return true; + } + if ( o == null || getClass() != o.getClass() ) + { + return false; + } + + BytesValue values = (BytesValue) o; + return Arrays.equals(val, values.val); + } + + @Override + public int hashCode() + { + return Arrays.hashCode(val); + } + + @Override + public String toString(Format valueFormat) + { + StringBuilder s = new StringBuilder("#"); + for (byte b : val) + { + if (b < 0x10) + { + s.append('0'); + } + s.append(Integer.toHexString(b)); + } + return maybeWithType( + valueFormat.includeType(), + s.toString() + ); + } +} 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 3568e0c187..5b4c2ed368 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 @@ -150,6 +150,12 @@ public Object asObject() throw new Uncoercible( type().name(), "Java Object" ); } + @Override + public byte[] asByteArray() + { + throw new Uncoercible( type().name(), "Byte array" ); + } + @Override public Number asNumber() { 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 7f8dbfa3dd..cbf2a3316e 100644 --- a/driver/src/main/java/org/neo4j/driver/v1/Value.java +++ b/driver/src/main/java/org/neo4j/driver/v1/Value.java @@ -193,6 +193,12 @@ public interface Value extends MapAccessor, MapAccessorWithDefaultValue */ boolean asBoolean(); + /** + * @return the value as a Java byte array, if possible. + * @throws Uncoercible if value types are incompatible. + */ + byte[] asByteArray(); + /** * @return the value as a Java String, if possible. * @throws Uncoercible if value types are incompatible. 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 e83ef25d29..4a36871c47 100644 --- a/driver/src/main/java/org/neo4j/driver/v1/Values.java +++ b/driver/src/main/java/org/neo4j/driver/v1/Values.java @@ -29,6 +29,7 @@ import org.neo4j.driver.internal.AsValue; import org.neo4j.driver.internal.value.BooleanValue; +import org.neo4j.driver.internal.value.BytesValue; import org.neo4j.driver.internal.value.FloatValue; import org.neo4j.driver.internal.value.IntegerValue; import org.neo4j.driver.internal.value.ListValue; @@ -84,6 +85,7 @@ public static Value value( Object value ) if ( value instanceof Iterable ) { return value( (Iterable) value ); } if ( value instanceof Iterator ) { return value( (Iterator) value ); } + if ( value instanceof byte[] ) { return value( (byte[]) value ); } if ( value instanceof boolean[] ) { return value( (boolean[]) value ); } if ( value instanceof String[] ) { return value( (String[]) value ); } if ( value instanceof long[] ) { return value( (long[]) value ); } @@ -96,6 +98,7 @@ public static Value value( Object value ) throw new ClientException( "Unable to convert " + value.getClass().getName() + " to Neo4j Value." ); } + public static Value[] values( final Object... input ) { Value[] values = new Value[input.length]; @@ -114,6 +117,11 @@ public static Value value( Value... input ) return new ListValue( values ); } + public static BytesValue value( byte... input ) + { + return new BytesValue( input ); + } + public static Value value( String... input ) { StringValue[] values = new StringValue[input.length]; 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 7058dc3549..93f961f3c1 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 @@ -33,6 +33,8 @@ public interface TypeSystem Type BOOLEAN(); + Type BYTES(); + Type STRING(); Type NUMBER(); diff --git a/driver/src/test/java/org/neo4j/driver/internal/messaging/FragmentedMessageDeliveryTest.java b/driver/src/test/java/org/neo4j/driver/internal/messaging/FragmentedMessageDeliveryTest.java index 68618a5039..a3b2fd66d7 100644 --- a/driver/src/test/java/org/neo4j/driver/internal/messaging/FragmentedMessageDeliveryTest.java +++ b/driver/src/test/java/org/neo4j/driver/internal/messaging/FragmentedMessageDeliveryTest.java @@ -117,7 +117,6 @@ private ReadableByteChannel packet( ByteBuffer buffer ) new ByteArrayInputStream( buffer.array() ) ); } - private ReadableByteChannel packets( final ReadableByteChannel... channels ) { @@ -147,19 +146,17 @@ public void close() throws IOException private byte[] serialize( Message... msgs ) throws IOException { + final ByteArrayOutputStream out = new ByteArrayOutputStream( 128 ); - final ByteArrayOutputStream out = new ByteArrayOutputStream( 128 ); - - ChunkedOutput output = new ChunkedOutput( chunkSize + 2 /* for chunk header */, Channels.newChannel( out ) ); - + ChunkedOutput output = new ChunkedOutput( chunkSize + 2 /* for chunk header */, Channels.newChannel( out ) ); PackStreamMessageFormatV1.Writer writer = - new PackStreamMessageFormatV1.Writer( output, output.messageBoundaryHook() ); - for ( Message message : messages ) - { - writer.write( message ); - } - writer.flush(); + new PackStreamMessageFormatV1.Writer( output, output.messageBoundaryHook(), true ); + for ( Message message : messages ) + { + writer.write( message ); + } + writer.flush(); - return out.toByteArray(); + return out.toByteArray(); } } 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 65e312a79e..b011e3a693 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 @@ -135,7 +135,7 @@ private void assertSerializes( Message... messages ) throws IOException { // Pack final ByteArrayOutputStream out = new ByteArrayOutputStream( 128 ); - MessageFormat.Writer writer = format.newWriter( Channels.newChannel( out ) ); + MessageFormat.Writer writer = format.newWriter( Channels.newChannel( out ), true ); for ( Message message : messages ) { writer.write( message ); diff --git a/driver/src/test/java/org/neo4j/driver/internal/value/BytesValueTest.java b/driver/src/test/java/org/neo4j/driver/internal/value/BytesValueTest.java new file mode 100644 index 0000000000..fe930bee1a --- /dev/null +++ b/driver/src/test/java/org/neo4j/driver/internal/value/BytesValueTest.java @@ -0,0 +1,99 @@ +/* + * Copyright (c) 2002-2017 "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.junit.Test; +import org.neo4j.driver.internal.types.InternalTypeSystem; +import org.neo4j.driver.internal.types.TypeConstructor; +import org.neo4j.driver.v1.Value; +import org.neo4j.driver.v1.types.TypeSystem; + +import static org.hamcrest.CoreMatchers.equalTo; +import static org.hamcrest.CoreMatchers.notNullValue; +import static org.junit.Assert.assertFalse; +import static org.junit.Assert.assertThat; + +public class BytesValueTest +{ + public static final byte[] TEST_BYTES = "0123".getBytes(); + + TypeSystem typeSystem = InternalTypeSystem.TYPE_SYSTEM; + + @Test + public void testBytesValue() throws Exception + { + // Given + BytesValue value = new BytesValue( TEST_BYTES ); + + // Then + assertThat( value.asObject(), equalTo( TEST_BYTES ) ); + } + + @Test + public void testIsBytes() throws Exception + { + // Given + BytesValue value = new BytesValue( TEST_BYTES ); + + // Then + assertThat( typeSystem.BYTES().isTypeOf( value ), equalTo( true ) ); + } + + @Test + public void testEquals() throws Exception + { + // Given + BytesValue firstValue = new BytesValue( TEST_BYTES ); + BytesValue secondValue = new BytesValue( TEST_BYTES ); + + // Then + assertThat( firstValue, equalTo( secondValue ) ); + } + + @Test + public void testHashCode() throws Exception + { + // Given + BytesValue value = new BytesValue( TEST_BYTES ); + + // Then + assertThat( value.hashCode(), notNullValue() ); + } + + @Test + public void shouldNotBeNull() + { + Value value = new BytesValue( TEST_BYTES ); + assertFalse( value.isNull() ); + } + + @Test + public void shouldTypeAsString() + { + InternalValue value = new BytesValue( TEST_BYTES ); + assertThat( value.typeConstructor(), equalTo( TypeConstructor.BYTES_TyCon ) ); + } + + @Test + public void shouldHaveBytesType() + { + InternalValue value = new BytesValue( TEST_BYTES ); + assertThat( value.type(), equalTo( InternalTypeSystem.TYPE_SYSTEM.BYTES() ) ); + } +} diff --git a/driver/src/test/java/org/neo4j/driver/v1/integration/ParametersIT.java b/driver/src/test/java/org/neo4j/driver/v1/integration/ParametersIT.java index 16d212259b..e0babc32d7 100644 --- a/driver/src/test/java/org/neo4j/driver/v1/integration/ParametersIT.java +++ b/driver/src/test/java/org/neo4j/driver/v1/integration/ParametersIT.java @@ -22,19 +22,27 @@ import org.junit.Test; import org.junit.rules.ExpectedException; +import org.neo4j.driver.internal.util.ServerVersion; import org.neo4j.driver.v1.Record; import org.neo4j.driver.v1.StatementResult; import org.neo4j.driver.v1.Value; import org.neo4j.driver.v1.exceptions.ClientException; +import org.neo4j.driver.v1.exceptions.ServiceUnavailableException; import org.neo4j.driver.v1.types.Node; import org.neo4j.driver.v1.types.Path; import org.neo4j.driver.v1.types.Relationship; import org.neo4j.driver.v1.util.TestNeo4jSession; +import static org.hamcrest.CoreMatchers.containsString; import static org.hamcrest.CoreMatchers.equalTo; +import static org.hamcrest.CoreMatchers.instanceOf; import static org.hamcrest.MatcherAssert.assertThat; -import static org.neo4j.driver.v1.Values.parameters; +import static org.junit.Assert.fail; +import static org.junit.Assume.assumeFalse; +import static org.junit.Assume.assumeTrue; +import static org.neo4j.driver.internal.util.ServerVersion.version; import static org.neo4j.driver.v1.Values.ofValue; +import static org.neo4j.driver.v1.Values.parameters; public class ParametersIT { @@ -142,6 +150,55 @@ public void shouldBeAbleToSetAndReturnDoubleProperty() } } + private boolean supportsBytes() + { + String versionString = session.run( "RETURN 1" ).consume().server().version(); + return version( versionString ).greaterThanOrEqual( ServerVersion.v3_2_0 ); + } + + @Test + public void shouldBeAbleToSetAndReturnBytesProperty() + { + assumeTrue( supportsBytes() ); + + // Given + byte[] byteArray = "hello, world".getBytes(); + + // When + StatementResult result = session.run( + "CREATE (a {value:{value}}) RETURN a.value", parameters( "value", byteArray ) ); + + // Then + for ( Record record : result.list() ) + { + Value value = record.get( "a.value" ); + assertThat( value.hasType( session.typeSystem().BYTES() ), equalTo( true ) ); + assertThat( value.asByteArray(), equalTo( byteArray ) ); + } + } + + @Test + public void shouldThrowExceptionWhenServerDoesNotSupportBytes() + { + assumeFalse( supportsBytes() ); + + // Given + byte[] byteArray = "hello, world".getBytes(); + + // When + try + { + StatementResult result = session.run( + "CREATE (a {value:{value}}) RETURN a.value", parameters( "value", byteArray ) ); + fail( "Should not be able to pack bytes" ); + } + catch( Throwable e ) + { + assertThat( e, instanceOf( ServiceUnavailableException.class ) ); + assertThat( e.getMessage(), containsString( "Packing bytes is not supported" ) ); + } + } + @Test public void shouldBeAbleToSetAndReturnStringProperty() { diff --git a/driver/src/test/java/org/neo4j/driver/v1/util/Neo4jRunner.java b/driver/src/test/java/org/neo4j/driver/v1/util/Neo4jRunner.java index e7fdc7a803..a8f344bfcc 100644 --- a/driver/src/test/java/org/neo4j/driver/v1/util/Neo4jRunner.java +++ b/driver/src/test/java/org/neo4j/driver/v1/util/Neo4jRunner.java @@ -51,7 +51,7 @@ public class Neo4jRunner private static final boolean debug = true; - private static final String DEFAULT_NEOCTRL_ARGS = "-e 3.1.2"; + private static final String DEFAULT_NEOCTRL_ARGS = "-e 3.2.0"; public static final String NEOCTRL_ARGS = System.getProperty( "neoctrl.args", DEFAULT_NEOCTRL_ARGS ); public static final URI DEFAULT_URI = URI.create( "bolt://localhost:7687" ); public static final BoltServerAddress DEFAULT_ADDRESS = BoltServerAddress.from( DEFAULT_URI );