diff --git a/driver/clirr-ignored-differences.xml b/driver/clirr-ignored-differences.xml index 8d9be682d4..e623d9ff64 100644 --- a/driver/clirr-ignored-differences.xml +++ b/driver/clirr-ignored-differences.xml @@ -86,4 +86,22 @@ org.neo4j.driver.Config$TrustStrategy trustCustomCertificateSignedBy(java.io.File[]) + + org/neo4j/driver/types/Entity + 7012 + java.lang.String elementId() + + + + org/neo4j/driver/types/Relationship + 7012 + java.lang.String endNodeElementId() + + + + org/neo4j/driver/types/Relationship + 7012 + java.lang.String startNodeElementId() + + diff --git a/driver/src/main/java/org/neo4j/driver/internal/InternalEntity.java b/driver/src/main/java/org/neo4j/driver/internal/InternalEntity.java index bc7e194117..ee719e6cda 100644 --- a/driver/src/main/java/org/neo4j/driver/internal/InternalEntity.java +++ b/driver/src/main/java/org/neo4j/driver/internal/InternalEntity.java @@ -19,25 +19,27 @@ package org.neo4j.driver.internal; import java.util.Map; +import java.util.function.Function; +import org.neo4j.driver.Value; +import org.neo4j.driver.Values; import org.neo4j.driver.internal.util.Extract; import org.neo4j.driver.internal.util.Iterables; import org.neo4j.driver.internal.value.MapValue; -import org.neo4j.driver.Value; -import org.neo4j.driver.Values; import org.neo4j.driver.types.Entity; -import java.util.function.Function; import static org.neo4j.driver.Values.ofObject; public abstract class InternalEntity implements Entity, AsValue { private final long id; + private final String elementId; private final Map properties; - public InternalEntity( long id, Map properties ) + public InternalEntity( long id, String elementId, Map properties ) { this.id = id; + this.elementId = elementId; this.properties = properties; } @@ -47,6 +49,12 @@ public long id() return id; } + @Override + public String elementId() + { + return elementId; + } + @Override public int size() { diff --git a/driver/src/main/java/org/neo4j/driver/internal/InternalNode.java b/driver/src/main/java/org/neo4j/driver/internal/InternalNode.java index e9a6938f1a..f0c4a992c2 100644 --- a/driver/src/main/java/org/neo4j/driver/internal/InternalNode.java +++ b/driver/src/main/java/org/neo4j/driver/internal/InternalNode.java @@ -22,9 +22,9 @@ import java.util.Collections; import java.util.Map; +import org.neo4j.driver.Value; import org.neo4j.driver.internal.value.NodeValue; import org.neo4j.driver.types.Node; -import org.neo4j.driver.Value; /** * {@link Node} implementation that directly contains labels and properties. @@ -35,12 +35,12 @@ public class InternalNode extends InternalEntity implements Node public InternalNode( long id ) { - this( id, Collections.emptyList(), Collections.emptyMap() ); + this( id, String.valueOf( id ), Collections.emptyList(), Collections.emptyMap() ); } - public InternalNode( long id, Collection labels, Map properties ) + public InternalNode( long id, String elementId, Collection labels, Map properties ) { - super( id, properties ); + super( id, elementId, properties ); this.labels = labels; } diff --git a/driver/src/main/java/org/neo4j/driver/internal/InternalRelationship.java b/driver/src/main/java/org/neo4j/driver/internal/InternalRelationship.java index dfe00395e2..c256bf5c49 100644 --- a/driver/src/main/java/org/neo4j/driver/internal/InternalRelationship.java +++ b/driver/src/main/java/org/neo4j/driver/internal/InternalRelationship.java @@ -21,9 +21,9 @@ import java.util.Collections; import java.util.Map; +import org.neo4j.driver.Value; import org.neo4j.driver.internal.value.RelationshipValue; import org.neo4j.driver.types.Relationship; -import org.neo4j.driver.Value; /** * {@link Relationship} implementation that directly contains type and properties. @@ -31,20 +31,24 @@ public class InternalRelationship extends InternalEntity implements Relationship { private long start; + private String startElementId; private long end; + private String endElementId; private final String type; - public InternalRelationship( long id, long start, long end, String type ) + public InternalRelationship( long id, String elementId, long start, String startElementId, long end, String endElementId, String type ) { - this( id, start, end, type, Collections.emptyMap() ); + this( id, elementId, start, startElementId, end, endElementId, type, Collections.emptyMap() ); } - public InternalRelationship( long id, long start, long end, String type, - Map properties ) + public InternalRelationship( long id, String elementId, long start, String startElementId, long end, String endElementId, String type, + Map properties ) { - super( id, properties ); + super( id, elementId, properties ); this.start = start; + this.startElementId = startElementId; this.end = end; + this.endElementId = endElementId; this.type = type; } @@ -54,11 +58,15 @@ public boolean hasType( String relationshipType ) return type().equals( relationshipType ); } - /** Modify the start/end identities of this relationship */ - public void setStartAndEnd( long start, long end ) + /** + * Modify the start/end identities of this relationship + */ + public void setStartAndEnd( long start, String startElementId, long end, String endElementId ) { this.start = start; + this.startElementId = startElementId; this.end = end; + this.endElementId = endElementId; } @Override @@ -67,12 +75,24 @@ public long startNodeId() return start; } + @Override + public String startNodeElementId() + { + return startElementId; + } + @Override public long endNodeId() { return end; } + @Override + public String endNodeElementId() + { + return endElementId; + } + @Override public String type() { diff --git a/driver/src/main/java/org/neo4j/driver/internal/async/connection/BoltProtocolUtil.java b/driver/src/main/java/org/neo4j/driver/internal/async/connection/BoltProtocolUtil.java index 5576de874f..d3109fb7c9 100644 --- a/driver/src/main/java/org/neo4j/driver/internal/async/connection/BoltProtocolUtil.java +++ b/driver/src/main/java/org/neo4j/driver/internal/async/connection/BoltProtocolUtil.java @@ -22,10 +22,10 @@ import org.neo4j.driver.internal.messaging.BoltProtocolVersion; import org.neo4j.driver.internal.messaging.v3.BoltProtocolV3; -import org.neo4j.driver.internal.messaging.v4.BoltProtocolV4; import org.neo4j.driver.internal.messaging.v41.BoltProtocolV41; import org.neo4j.driver.internal.messaging.v42.BoltProtocolV42; import org.neo4j.driver.internal.messaging.v44.BoltProtocolV44; +import org.neo4j.driver.internal.messaging.v5.BoltProtocolV5; import static io.netty.buffer.Unpooled.copyInt; import static io.netty.buffer.Unpooled.unreleasableBuffer; @@ -42,9 +42,9 @@ public final class BoltProtocolUtil private static final ByteBuf HANDSHAKE_BUF = unreleasableBuffer( copyInt( BOLT_MAGIC_PREAMBLE, + BoltProtocolV5.VERSION.toInt(), BoltProtocolV44.VERSION.toIntRange( BoltProtocolV42.VERSION ), BoltProtocolV41.VERSION.toInt(), - BoltProtocolV4.VERSION.toInt(), BoltProtocolV3.VERSION.toInt() ) ).asReadOnly(); private static final String HANDSHAKE_STRING = createHandshakeString(); diff --git a/driver/src/main/java/org/neo4j/driver/internal/messaging/BoltProtocol.java b/driver/src/main/java/org/neo4j/driver/internal/messaging/BoltProtocol.java index dee84f2a23..1a47804d91 100644 --- a/driver/src/main/java/org/neo4j/driver/internal/messaging/BoltProtocol.java +++ b/driver/src/main/java/org/neo4j/driver/internal/messaging/BoltProtocol.java @@ -41,6 +41,7 @@ import org.neo4j.driver.internal.messaging.v42.BoltProtocolV42; import org.neo4j.driver.internal.messaging.v43.BoltProtocolV43; import org.neo4j.driver.internal.messaging.v44.BoltProtocolV44; +import org.neo4j.driver.internal.messaging.v5.BoltProtocolV5; import org.neo4j.driver.internal.spi.Connection; import static org.neo4j.driver.internal.async.connection.ChannelAttributes.protocolVersion; @@ -171,6 +172,10 @@ else if ( BoltProtocolV44.VERSION.equals( version ) ) { return BoltProtocolV44.INSTANCE; } + else if ( BoltProtocolV5.VERSION.equals( version ) ) + { + return BoltProtocolV5.INSTANCE; + } throw new ClientException( "Unknown protocol version: " + version ); } } diff --git a/driver/src/main/java/org/neo4j/driver/internal/messaging/common/CommonValueUnpacker.java b/driver/src/main/java/org/neo4j/driver/internal/messaging/common/CommonValueUnpacker.java index 768635e609..928acfe8f3 100644 --- a/driver/src/main/java/org/neo4j/driver/internal/messaging/common/CommonValueUnpacker.java +++ b/driver/src/main/java/org/neo4j/driver/internal/messaging/common/CommonValueUnpacker.java @@ -91,7 +91,8 @@ public class CommonValueUnpacker implements ValueUnpacker public static final byte UNBOUND_RELATIONSHIP = 'r'; public static final byte PATH = 'P'; - public static final int NODE_FIELDS = 3; + private static final int NODE_FIELDS = 3; + private static final int RELATIONSHIP_FIELDS = 5; protected final PackStream.Unpacker unpacker; @@ -141,7 +142,7 @@ public Value[] unpackArray() throws IOException return values; } - private Value unpack() throws IOException + protected Value unpack() throws IOException { PackType type = unpacker.peekNextType(); switch ( type ) @@ -182,7 +183,7 @@ private Value unpack() throws IOException throw new IOException( "Unknown value type: " + type ); } - protected Value unpackStruct( long size, byte type ) throws IOException + private Value unpackStruct( long size, byte type ) throws IOException { switch ( type ) { @@ -214,11 +215,11 @@ protected Value unpackStruct( long size, byte type ) throws IOException ensureCorrectStructSize( TypeConstructor.POINT, POINT_3D_STRUCT_SIZE, size ); return unpackPoint3D(); case NODE: - ensureCorrectStructSize( TypeConstructor.NODE, NODE_FIELDS, size ); + ensureCorrectStructSize( TypeConstructor.NODE, getNodeFields(), size ); InternalNode adapted = unpackNode(); return new NodeValue( adapted ); case RELATIONSHIP: - ensureCorrectStructSize( TypeConstructor.RELATIONSHIP, 5, size ); + ensureCorrectStructSize( TypeConstructor.RELATIONSHIP, getRelationshipFields(), size ); return unpackRelationship(); case PATH: ensureCorrectStructSize( TypeConstructor.PATH, 3, size ); @@ -228,7 +229,7 @@ protected Value unpackStruct( long size, byte type ) throws IOException } } - private Value unpackRelationship() throws IOException + protected Value unpackRelationship() throws IOException { long urn = unpacker.unpackLong(); long startUrn = unpacker.unpackLong(); @@ -236,11 +237,12 @@ private Value unpackRelationship() throws IOException String relType = unpacker.unpackString(); Map props = unpackMap(); - InternalRelationship adapted = new InternalRelationship( urn, startUrn, endUrn, relType, props ); + InternalRelationship adapted = + new InternalRelationship( urn, String.valueOf( urn ), startUrn, String.valueOf( startUrn ), endUrn, String.valueOf( endUrn ), relType, props ); return new RelationshipValue( adapted ); } - private InternalNode unpackNode() throws IOException + protected InternalNode unpackNode() throws IOException { long urn = unpacker.unpackLong(); @@ -258,16 +260,16 @@ private InternalNode unpackNode() throws IOException props.put( key, unpack() ); } - return new InternalNode( urn, labels, props ); + return new InternalNode( urn, String.valueOf( urn ), labels, props ); } - private Value unpackPath() throws IOException + protected Value unpackPath() throws IOException { // List of unique nodes Node[] uniqNodes = new Node[(int) unpacker.unpackListHeader()]; for ( int i = 0; i < uniqNodes.length; i++ ) { - ensureCorrectStructSize( TypeConstructor.NODE, NODE_FIELDS, unpacker.unpackStructHeader() ); + ensureCorrectStructSize( TypeConstructor.NODE, getNodeFields(), unpacker.unpackStructHeader() ); ensureCorrectStructSignature( "NODE", NODE, unpacker.unpackStructSignature() ); uniqNodes[i] = unpackNode(); } @@ -281,7 +283,7 @@ private Value unpackPath() throws IOException long id = unpacker.unpackLong(); String relType = unpacker.unpackString(); Map props = unpackMap(); - uniqRels[i] = new InternalRelationship( id, -1, -1, relType, props ); + uniqRels[i] = new InternalRelationship( id, String.valueOf( id ), -1, String.valueOf( -1 ), -1, String.valueOf( -1 ), relType, props ); } // Path sequence @@ -303,12 +305,12 @@ private Value unpackPath() throws IOException if ( relIdx < 0 ) { rel = uniqRels[(-relIdx) - 1]; // -1 because rel idx are 1-indexed - rel.setStartAndEnd( nextNode.id(), prevNode.id() ); + rel.setStartAndEnd( nextNode.id(), String.valueOf( nextNode.id() ), prevNode.id(), String.valueOf( prevNode.id() ) ); } else { rel = uniqRels[relIdx - 1]; - rel.setStartAndEnd( prevNode.id(), nextNode.id() ); + rel.setStartAndEnd( prevNode.id(), String.valueOf( prevNode.id() ), nextNode.id(), String.valueOf( nextNode.id() ) ); } nodes[i + 1] = nextNode; @@ -330,7 +332,7 @@ protected final void ensureCorrectStructSize( TypeConstructor typeConstructor, i } } - private void ensureCorrectStructSignature( String structName, byte expected, byte actual ) + protected void ensureCorrectStructSignature( String structName, byte expected, byte actual ) { if ( expected != actual ) { @@ -417,6 +419,14 @@ private static ZonedDateTime newZonedDateTime( long epochSecondLocal, long nano, LocalDateTime localDateTime = LocalDateTime.ofInstant( instant, UTC ); return ZonedDateTime.of( localDateTime, zoneId ); } -} + protected int getNodeFields() + { + return NODE_FIELDS; + } + protected int getRelationshipFields() + { + return RELATIONSHIP_FIELDS; + } +} diff --git a/driver/src/main/java/org/neo4j/driver/internal/messaging/v5/BoltProtocolV5.java b/driver/src/main/java/org/neo4j/driver/internal/messaging/v5/BoltProtocolV5.java new file mode 100644 index 0000000000..1cfad95b64 --- /dev/null +++ b/driver/src/main/java/org/neo4j/driver/internal/messaging/v5/BoltProtocolV5.java @@ -0,0 +1,42 @@ +/* + * Copyright (c) "Neo4j" + * Neo4j Sweden AB [http://neo4j.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.v5; + +import org.neo4j.driver.internal.messaging.BoltProtocol; +import org.neo4j.driver.internal.messaging.BoltProtocolVersion; +import org.neo4j.driver.internal.messaging.MessageFormat; +import org.neo4j.driver.internal.messaging.v44.BoltProtocolV44; + +public class BoltProtocolV5 extends BoltProtocolV44 +{ + public static final BoltProtocolVersion VERSION = new BoltProtocolVersion( 5, 0 ); + public static final BoltProtocol INSTANCE = new BoltProtocolV5(); + + @Override + public MessageFormat createMessageFormat() + { + return new MessageFormatV5(); + } + + @Override + public BoltProtocolVersion version() + { + return VERSION; + } +} diff --git a/driver/src/main/java/org/neo4j/driver/internal/messaging/v5/MessageFormatV5.java b/driver/src/main/java/org/neo4j/driver/internal/messaging/v5/MessageFormatV5.java new file mode 100644 index 0000000000..2fd76cbd25 --- /dev/null +++ b/driver/src/main/java/org/neo4j/driver/internal/messaging/v5/MessageFormatV5.java @@ -0,0 +1,38 @@ +/* + * Copyright (c) "Neo4j" + * Neo4j Sweden AB [http://neo4j.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.v5; + +import org.neo4j.driver.internal.messaging.MessageFormat; +import org.neo4j.driver.internal.packstream.PackInput; +import org.neo4j.driver.internal.packstream.PackOutput; + +public class MessageFormatV5 implements MessageFormat +{ + @Override + public Writer newWriter( PackOutput output ) + { + return new MessageWriterV5( output ); + } + + @Override + public Reader newReader( PackInput input ) + { + return new MessageReaderV5( input ); + } +} diff --git a/driver/src/main/java/org/neo4j/driver/internal/messaging/v5/MessageReaderV5.java b/driver/src/main/java/org/neo4j/driver/internal/messaging/v5/MessageReaderV5.java new file mode 100644 index 0000000000..480fa4f4be --- /dev/null +++ b/driver/src/main/java/org/neo4j/driver/internal/messaging/v5/MessageReaderV5.java @@ -0,0 +1,30 @@ +/* + * Copyright (c) "Neo4j" + * Neo4j Sweden AB [http://neo4j.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.v5; + +import org.neo4j.driver.internal.messaging.common.CommonMessageReader; +import org.neo4j.driver.internal.packstream.PackInput; + +public class MessageReaderV5 extends CommonMessageReader +{ + public MessageReaderV5( PackInput input ) + { + super( new ValueUnpackerV5( input ) ); + } +} diff --git a/driver/src/main/java/org/neo4j/driver/internal/messaging/v5/MessageWriterV5.java b/driver/src/main/java/org/neo4j/driver/internal/messaging/v5/MessageWriterV5.java new file mode 100644 index 0000000000..d3cc895d9d --- /dev/null +++ b/driver/src/main/java/org/neo4j/driver/internal/messaging/v5/MessageWriterV5.java @@ -0,0 +1,74 @@ +/* + * Copyright (c) "Neo4j" + * Neo4j Sweden AB [http://neo4j.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.v5; + +import java.util.Map; + +import org.neo4j.driver.internal.messaging.AbstractMessageWriter; +import org.neo4j.driver.internal.messaging.MessageEncoder; +import org.neo4j.driver.internal.messaging.common.CommonValuePacker; +import org.neo4j.driver.internal.messaging.encode.BeginMessageEncoder; +import org.neo4j.driver.internal.messaging.encode.CommitMessageEncoder; +import org.neo4j.driver.internal.messaging.encode.DiscardMessageEncoder; +import org.neo4j.driver.internal.messaging.encode.GoodbyeMessageEncoder; +import org.neo4j.driver.internal.messaging.encode.HelloMessageEncoder; +import org.neo4j.driver.internal.messaging.encode.PullMessageEncoder; +import org.neo4j.driver.internal.messaging.encode.ResetMessageEncoder; +import org.neo4j.driver.internal.messaging.encode.RollbackMessageEncoder; +import org.neo4j.driver.internal.messaging.encode.RouteV44MessageEncoder; +import org.neo4j.driver.internal.messaging.encode.RunWithMetadataMessageEncoder; +import org.neo4j.driver.internal.messaging.request.BeginMessage; +import org.neo4j.driver.internal.messaging.request.CommitMessage; +import org.neo4j.driver.internal.messaging.request.DiscardMessage; +import org.neo4j.driver.internal.messaging.request.GoodbyeMessage; +import org.neo4j.driver.internal.messaging.request.HelloMessage; +import org.neo4j.driver.internal.messaging.request.PullMessage; +import org.neo4j.driver.internal.messaging.request.ResetMessage; +import org.neo4j.driver.internal.messaging.request.RollbackMessage; +import org.neo4j.driver.internal.messaging.request.RouteMessage; +import org.neo4j.driver.internal.messaging.request.RunWithMetadataMessage; +import org.neo4j.driver.internal.packstream.PackOutput; +import org.neo4j.driver.internal.util.Iterables; + +public class MessageWriterV5 extends AbstractMessageWriter +{ + public MessageWriterV5( PackOutput output ) + { + super( new CommonValuePacker( output ), buildEncoders() ); + } + + private static Map buildEncoders() + { + Map result = Iterables.newHashMapWithSize( 9 ); + result.put( HelloMessage.SIGNATURE, new HelloMessageEncoder() ); + result.put( GoodbyeMessage.SIGNATURE, new GoodbyeMessageEncoder() ); + result.put( RunWithMetadataMessage.SIGNATURE, new RunWithMetadataMessageEncoder() ); + result.put( RouteMessage.SIGNATURE, new RouteV44MessageEncoder() ); + + result.put( DiscardMessage.SIGNATURE, new DiscardMessageEncoder() ); + result.put( PullMessage.SIGNATURE, new PullMessageEncoder() ); + + result.put( BeginMessage.SIGNATURE, new BeginMessageEncoder() ); + result.put( CommitMessage.SIGNATURE, new CommitMessageEncoder() ); + result.put( RollbackMessage.SIGNATURE, new RollbackMessageEncoder() ); + + result.put( ResetMessage.SIGNATURE, new ResetMessageEncoder() ); + return result; + } +} diff --git a/driver/src/main/java/org/neo4j/driver/internal/messaging/v5/ValueUnpackerV5.java b/driver/src/main/java/org/neo4j/driver/internal/messaging/v5/ValueUnpackerV5.java new file mode 100644 index 0000000000..8ac67f6b52 --- /dev/null +++ b/driver/src/main/java/org/neo4j/driver/internal/messaging/v5/ValueUnpackerV5.java @@ -0,0 +1,162 @@ +/* + * Copyright (c) "Neo4j" + * Neo4j Sweden AB [http://neo4j.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.v5; + +import java.io.IOException; +import java.util.ArrayList; +import java.util.Arrays; +import java.util.List; +import java.util.Map; + +import org.neo4j.driver.Value; +import org.neo4j.driver.internal.InternalNode; +import org.neo4j.driver.internal.InternalPath; +import org.neo4j.driver.internal.InternalRelationship; +import org.neo4j.driver.internal.messaging.common.CommonValueUnpacker; +import org.neo4j.driver.internal.packstream.PackInput; +import org.neo4j.driver.internal.types.TypeConstructor; +import org.neo4j.driver.internal.util.Iterables; +import org.neo4j.driver.internal.value.PathValue; +import org.neo4j.driver.internal.value.RelationshipValue; +import org.neo4j.driver.types.Node; +import org.neo4j.driver.types.Path; +import org.neo4j.driver.types.Relationship; + +public class ValueUnpackerV5 extends CommonValueUnpacker +{ + private static final int NODE_FIELDS = 4; + private static final int RELATIONSHIP_FIELDS = 8; + + public ValueUnpackerV5( PackInput input ) + { + super( input ); + } + + @Override + protected int getNodeFields() + { + return NODE_FIELDS; + } + + @Override + protected int getRelationshipFields() + { + return RELATIONSHIP_FIELDS; + } + + @Override + protected InternalNode unpackNode() throws IOException + { + long urn = unpacker.unpackLongOrDefaultOnNull( -1 ); + + int numLabels = (int) unpacker.unpackListHeader(); + List labels = new ArrayList<>( numLabels ); + for ( int i = 0; i < numLabels; i++ ) + { + labels.add( unpacker.unpackString() ); + } + int numProps = (int) unpacker.unpackMapHeader(); + Map props = Iterables.newHashMapWithSize( numProps ); + for ( int j = 0; j < numProps; j++ ) + { + String key = unpacker.unpackString(); + props.put( key, unpack() ); + } + + String elementId = unpacker.unpackString(); + + return new InternalNode( urn, elementId, labels, props ); + } + + @Override + protected Value unpackPath() throws IOException + { + // List of unique nodes + Node[] uniqNodes = new Node[(int) unpacker.unpackListHeader()]; + for ( int i = 0; i < uniqNodes.length; i++ ) + { + ensureCorrectStructSize( TypeConstructor.NODE, getNodeFields(), unpacker.unpackStructHeader() ); + ensureCorrectStructSignature( "NODE", NODE, unpacker.unpackStructSignature() ); + uniqNodes[i] = unpackNode(); + } + + // List of unique relationships, without start/end information + InternalRelationship[] uniqRels = new InternalRelationship[(int) unpacker.unpackListHeader()]; + for ( int i = 0; i < uniqRels.length; i++ ) + { + ensureCorrectStructSize( TypeConstructor.RELATIONSHIP, 4, unpacker.unpackStructHeader() ); + ensureCorrectStructSignature( "UNBOUND_RELATIONSHIP", UNBOUND_RELATIONSHIP, unpacker.unpackStructSignature() ); + Long id = unpacker.unpackLongOrDefaultOnNull( -1 ); + String relType = unpacker.unpackString(); + Map props = unpackMap(); + String elementId = unpacker.unpackString(); + uniqRels[i] = new InternalRelationship( id, elementId, -1, String.valueOf( -1 ), -1, String.valueOf( -1 ), relType, props ); + } + + // Path sequence + int length = (int) unpacker.unpackListHeader(); + + // Knowing the sequence length, we can create the arrays that will represent the nodes, rels and segments in their "path order" + Path.Segment[] segments = new Path.Segment[length / 2]; + Node[] nodes = new Node[segments.length + 1]; + Relationship[] rels = new Relationship[segments.length]; + + Node prevNode = uniqNodes[0], nextNode; // Start node is always 0, and isn't encoded in the sequence + nodes[0] = prevNode; + InternalRelationship rel; + for ( int i = 0; i < segments.length; i++ ) + { + int relIdx = (int) unpacker.unpackLong(); + nextNode = uniqNodes[(int) unpacker.unpackLong()]; + // Negative rel index means this rel was traversed "inversed" from its direction + if ( relIdx < 0 ) + { + rel = uniqRels[(-relIdx) - 1]; // -1 because rel idx are 1-indexed + rel.setStartAndEnd( nextNode.id(), nextNode.elementId(), prevNode.id(), prevNode.elementId() ); + } + else + { + rel = uniqRels[relIdx - 1]; + rel.setStartAndEnd( prevNode.id(), prevNode.elementId(), nextNode.id(), nextNode.elementId() ); + } + + nodes[i + 1] = nextNode; + rels[i] = rel; + segments[i] = new InternalPath.SelfContainedSegment( prevNode, rel, nextNode ); + prevNode = nextNode; + } + return new PathValue( new InternalPath( Arrays.asList( segments ), Arrays.asList( nodes ), Arrays.asList( rels ) ) ); + } + + @Override + protected Value unpackRelationship() throws IOException + { + long urn = unpacker.unpackLongOrDefaultOnNull( -1 ); + long startUrn = unpacker.unpackLongOrDefaultOnNull( -1 ); + long endUrn = unpacker.unpackLongOrDefaultOnNull( -1 ); + String relType = unpacker.unpackString(); + Map props = unpackMap(); + String elementId = unpacker.unpackString(); + String startElementId = unpacker.unpackString(); + String endElementId = unpacker.unpackString(); + + InternalRelationship adapted = new InternalRelationship( urn, elementId, startUrn, startElementId, endUrn, endElementId, relType, props ); + return new RelationshipValue( adapted ); + } +} 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 7ecd99f5bc..77a306e631 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 @@ -439,7 +439,8 @@ public long unpackListHeader() throws IOException case LIST_8: return unpackUINT8(); case LIST_16: return unpackUINT16(); case LIST_32: return unpackUINT32(); - default: throw new Unexpected( "Expected a list, but got: " + toHexString( markerByte & 0xFF )); + default: + throw new Unexpected( "Expected a list, but got: " + toHexString( markerByte & 0xFF ) ); } } @@ -449,27 +450,66 @@ public long unpackMapHeader() throws IOException final byte markerHighNibble = (byte) (markerByte & 0xF0); final byte markerLowNibble = (byte) (markerByte & 0x0F); - if ( markerHighNibble == TINY_MAP ) { return markerLowNibble; } - switch(markerByte) + if ( markerHighNibble == TINY_MAP ) { - case MAP_8: return unpackUINT8(); - case MAP_16: return unpackUINT16(); - case MAP_32: return unpackUINT32(); - default: throw new Unexpected( "Expected a map, but got: " + toHexString( markerByte )); + return markerLowNibble; + } + switch ( markerByte ) + { + case MAP_8: + return unpackUINT8(); + case MAP_16: + return unpackUINT16(); + case MAP_32: + return unpackUINT32(); + default: + throw new Unexpected( "Expected a map, but got: " + toHexString( markerByte ) ); + } + } + + public Long unpackLongOrDefaultOnNull( long defaultValue ) throws IOException + { + final byte markerByte = in.readByte(); + if ( markerByte >= MINUS_2_TO_THE_4 ) + { + return (long) markerByte; + } + switch ( markerByte ) + { + case INT_8: + return (long) in.readByte(); + case INT_16: + return (long) in.readShort(); + case INT_32: + return (long) in.readInt(); + case INT_64: + return in.readLong(); + case NULL: + return defaultValue; + default: + throw new Unexpected( "Expected an integer, but got: " + toHexString( markerByte ) ); } } public long unpackLong() throws IOException { final byte markerByte = in.readByte(); - if ( markerByte >= MINUS_2_TO_THE_4) { return markerByte; } - switch(markerByte) + if ( markerByte >= MINUS_2_TO_THE_4 ) + { + return markerByte; + } + switch ( markerByte ) { - case INT_8: return in.readByte(); - case INT_16: return in.readShort(); - case INT_32: return in.readInt(); - case INT_64: return in.readLong(); - default: throw new Unexpected( "Expected an integer, but got: " + toHexString( markerByte )); + case INT_8: + return in.readByte(); + case INT_16: + return in.readShort(); + case INT_32: + return in.readInt(); + case INT_64: + return in.readLong(); + default: + throw new Unexpected( "Expected an integer, but got: " + toHexString( markerByte ) ); } } diff --git a/driver/src/main/java/org/neo4j/driver/types/Entity.java b/driver/src/main/java/org/neo4j/driver/types/Entity.java index 06d7281648..55ce7a4279 100644 --- a/driver/src/main/java/org/neo4j/driver/types/Entity.java +++ b/driver/src/main/java/org/neo4j/driver/types/Entity.java @@ -28,12 +28,24 @@ public interface Entity extends MapAccessor { /** - * A unique id for this Entity. Ids are guaranteed to remain stable for the duration of the session they - * were found in, but may be re-used for other entities after that. As such, if you want a public identity to use - * for your entities, attaching an explicit 'id' property or similar persistent and unique identifier is a better - * choice. + * A unique id for this Entity. Ids are guaranteed to remain stable for the duration of the session they were found in, but may be re-used for other + * entities after that. As such, if you want a public identity to use for your entities, attaching an explicit 'id' property or similar persistent and + * unique identifier is a better choice. * - * @return the id of this entity + * @return the id of this entity, please note that negative values are considered to be invalid and may be returned when storage engine can only provide the + * {@link #elementId()} + * @deprecated superseded by {@link #elementId()} */ + @Deprecated long id(); + + /** + * A unique id for this Entity. + *

+ * It is recommended to attach an explicit 'id' property or similar persistent and unique identifier if you want a public identity to use for your + * entities. + * + * @return the id of this entity + */ + String elementId(); } diff --git a/driver/src/main/java/org/neo4j/driver/types/Relationship.java b/driver/src/main/java/org/neo4j/driver/types/Relationship.java index 241c3a838c..01a62196c6 100644 --- a/driver/src/main/java/org/neo4j/driver/types/Relationship.java +++ b/driver/src/main/java/org/neo4j/driver/types/Relationship.java @@ -26,16 +26,36 @@ public interface Relationship extends Entity { /** * Id of the node where this relationship starts. + * * @return the node id + * @deprecated superseded by {@link #startNodeElementId()} */ + @Deprecated long startNodeId(); + /** + * Id of the node where this relationship starts. + * + * @return the node id + */ + String startNodeElementId(); + /** * Id of the node where this relationship ends. + * * @return the node id + * @deprecated superseded by {@link #endNodeElementId()} */ + @Deprecated long endNodeId(); + /** + * Id of the node where this relationship ends. + * + * @return the node id + */ + String endNodeElementId(); + /** * Return the type of this relationship. * diff --git a/driver/src/test/java/org/neo4j/driver/TransactionConfigTest.java b/driver/src/test/java/org/neo4j/driver/TransactionConfigTest.java index aebb5bac20..2297d30428 100644 --- a/driver/src/test/java/org/neo4j/driver/TransactionConfigTest.java +++ b/driver/src/test/java/org/neo4j/driver/TransactionConfigTest.java @@ -70,7 +70,8 @@ void shouldDisallowMetadataWithIllegalValues() () -> TransactionConfig.builder().withMetadata( singletonMap( "key", new InternalNode( 1 ) ) ) ); assertThrows( ClientException.class, - () -> TransactionConfig.builder().withMetadata( singletonMap( "key", new InternalRelationship( 1, 1, 1, "" ) ) ) ); + () -> TransactionConfig.builder().withMetadata( + singletonMap( "key", new InternalRelationship( 1, String.valueOf( 1 ), 1, String.valueOf( 1 ), 1, String.valueOf( 1 ), "" ) ) ) ); assertThrows( ClientException.class, () -> TransactionConfig.builder().withMetadata( singletonMap( "key", new InternalPath( new InternalNode( 1 ) ) ) ) ); diff --git a/driver/src/test/java/org/neo4j/driver/internal/ExtractTest.java b/driver/src/test/java/org/neo4j/driver/internal/ExtractTest.java index 6f64c1fb0a..a996639d6c 100644 --- a/driver/src/test/java/org/neo4j/driver/internal/ExtractTest.java +++ b/driver/src/test/java/org/neo4j/driver/internal/ExtractTest.java @@ -29,10 +29,10 @@ import java.util.List; import java.util.Map; -import org.neo4j.driver.internal.util.Extract; -import org.neo4j.driver.internal.util.Iterables; import org.neo4j.driver.Value; import org.neo4j.driver.exceptions.ClientException; +import org.neo4j.driver.internal.util.Extract; +import org.neo4j.driver.internal.util.Iterables; import org.neo4j.driver.util.Pair; import static java.util.Arrays.asList; @@ -127,7 +127,7 @@ void testProperties() Map props = new HashMap<>(); props.put( "k1", value( 43 ) ); props.put( "k2", value( 42 ) ); - InternalNode node = new InternalNode( 42L, Collections.singletonList( "L" ), props ); + InternalNode node = new InternalNode( 42L, String.valueOf( 42L ), Collections.singletonList( "L" ), props ); // WHEN Iterable> properties = Extract.properties( node, Value::asInt ); @@ -181,7 +181,11 @@ void shouldExtractMapOfValues() void shouldFailToExtractMapOfValuesFromUnsupportedValues() { assertThrows( ClientException.class, () -> Extract.mapOfValues( singletonMap( "key", new InternalNode( 1 ) ) ) ); - assertThrows( ClientException.class, () -> Extract.mapOfValues( singletonMap( "key", new InternalRelationship( 1, 1, 1, "HI" ) ) ) ); + assertThrows( ClientException.class, + () -> Extract.mapOfValues( + singletonMap( "key", + new InternalRelationship( 1, String.valueOf( 1 ), 1, String.valueOf( 1 ), + 1, String.valueOf( 1 ), "HI" ) ) ) ); assertThrows( ClientException.class, () -> Extract.mapOfValues( singletonMap( "key", new InternalPath( new InternalNode( 1 ) ) ) ) ); } } diff --git a/driver/src/test/java/org/neo4j/driver/internal/InternalMapAccessorWithDefaultValueTest.java b/driver/src/test/java/org/neo4j/driver/internal/InternalMapAccessorWithDefaultValueTest.java index 406f2f6d1e..080803b66c 100644 --- a/driver/src/test/java/org/neo4j/driver/internal/InternalMapAccessorWithDefaultValueTest.java +++ b/driver/src/test/java/org/neo4j/driver/internal/InternalMapAccessorWithDefaultValueTest.java @@ -90,7 +90,7 @@ void shouldGetValueFromRecord() // Path Value defaultPathValue = new PathValue( new InternalPath( new InternalNode( 0L ), - new InternalRelationship( 0L, 0L, 1L, "T" ), + new InternalRelationship( 0L, String.valueOf( 0L ), 0L, String.valueOf( 0L ), 1L, String.valueOf( 1L ), "T" ), new InternalNode( 1L ) ) ); Value realPathValue = new PathValue( createPath() ); assertThat( record.get( "PathValue", defaultPathValue ), equalTo( realPathValue ) ); @@ -103,7 +103,8 @@ void shouldGetValueFromRecord() assertThat( record.get( wrongKey, defaultNodeValue ), equalTo( defaultNodeValue ) ); // Rel - Value defaultRelValue = new RelationshipValue( new InternalRelationship( 0L, 0L, 1L, "T" ) ); + Value defaultRelValue = + new RelationshipValue( new InternalRelationship( 0L, String.valueOf( 0L ), 0L, String.valueOf( 0L ), 1L, String.valueOf( 1L ), "T" ) ); Value realRelValue = new RelationshipValue( createRel() ); assertThat( record.get( "RelValue", defaultRelValue ), equalTo( realRelValue ) ); assertThat( record.get( wrongKey, defaultRelValue ), equalTo( defaultRelValue ) ); @@ -138,7 +139,7 @@ void shouldGetEntityFromRecord() assertThat( record.get( "NodeValue", defaultNodeEntity ), equalTo( (Entity) createNode() ) ); assertThat( record.get( wrongKey, defaultNodeEntity ), equalTo( defaultNodeEntity ) ); - Entity defaultRelEntity = new InternalRelationship( 0L, 0L, 1L, "T" ); + Entity defaultRelEntity = new InternalRelationship( 0L, String.valueOf( 0L ), 0L, String.valueOf( 0L ), 1L, String.valueOf( 1L ), "T" ); assertThat( record.get( "RelValue", defaultRelEntity ), equalTo( (Entity) createRel() ) ); assertThat( record.get( wrongKey, defaultRelEntity ), equalTo( defaultRelEntity ) ); } @@ -158,7 +159,7 @@ void shouldGetRelFromRecord() { Record record = createRecord(); - Relationship defaultRel = new InternalRelationship( 0L, 0L, 1L, "T" ); + Relationship defaultRel = new InternalRelationship( 0L, String.valueOf( 0L ), 0L, String.valueOf( 0L ), 1L, String.valueOf( 1L ), "T" ); assertThat( record.get( "RelValue", defaultRel ), equalTo( createRel() ) ); assertThat( record.get( wrongKey, defaultRel ), equalTo( defaultRel ) ); } @@ -170,7 +171,7 @@ void shouldGetPathFromRecord() Path defaultPath = new InternalPath( new InternalNode( 0L ), - new InternalRelationship( 0L, 0L, 1L, "T" ), + new InternalRelationship( 0L, String.valueOf( 0L ), 0L, String.valueOf( 0L ), 1L, String.valueOf( 1L ), "T" ), new InternalNode( 1L ) ); assertThat( record.get( "PathValue", defaultPath ), equalTo( createPath() ) ); assertThat( record.get( wrongKey, defaultPath ), equalTo( defaultPath ) ); @@ -270,7 +271,7 @@ void shouldGetFromNode() Map props = new HashMap<>(); props.put( "k1", value( 43 ) ); props.put( "k2", value( "hello world" ) ); - NodeValue nodeValue = new NodeValue( new InternalNode( 42L, Collections.singletonList( "L" ), props ) ); + NodeValue nodeValue = new NodeValue( new InternalNode( 42L, String.valueOf( 42L ), Collections.singletonList( "L" ), props ) ); assertThat( nodeValue.get( "k1", 0 ), equalTo( 43 ) ); assertThat( nodeValue.get( "k2", "" ), equalTo( "hello world" ) ); @@ -283,7 +284,8 @@ void shouldGetFromRel() Map props = new HashMap<>(); props.put( "k1", value( 43 ) ); props.put( "k2", value( "hello world" ) ); - RelationshipValue relValue = new RelationshipValue( new InternalRelationship( 0L, 0L, 1L, "T", props ) ); + RelationshipValue relValue = + new RelationshipValue( new InternalRelationship( 0L, String.valueOf( 0L ), 0L, String.valueOf( 0L ), 1L, String.valueOf( 1L ), "T", props ) ); assertThat( relValue.get( "k1", 0 ), equalTo( 43 ) ); assertThat( relValue.get( "k2", "" ), equalTo( "hello world" ) ); @@ -294,7 +296,7 @@ private Path createPath() { return new InternalPath( new InternalNode( 42L ), - new InternalRelationship( 43L, 42L, 44L, "T" ), + new InternalRelationship( 43L, String.valueOf( 43L ), 42L, String.valueOf( 42L ), 44L, String.valueOf( 44L ), "T" ), new InternalNode( 44L ) ); } @@ -305,7 +307,7 @@ private Node createNode() private Relationship createRel() { - return new InternalRelationship( 1L, 1L, 2L, "T" ); + return new InternalRelationship( 1L, String.valueOf( 1L ), 1L, String.valueOf( 1L ), 2L, String.valueOf( 2L ), "T" ); } private Map createMap() diff --git a/driver/src/test/java/org/neo4j/driver/internal/InternalNodeTest.java b/driver/src/test/java/org/neo4j/driver/internal/InternalNodeTest.java index ecccef961e..524b62f360 100644 --- a/driver/src/test/java/org/neo4j/driver/internal/InternalNodeTest.java +++ b/driver/src/test/java/org/neo4j/driver/internal/InternalNodeTest.java @@ -24,9 +24,9 @@ import java.util.HashMap; import java.util.Iterator; import java.util.Map; +import java.util.function.Function; import org.neo4j.driver.Value; -import java.util.function.Function; import static org.hamcrest.CoreMatchers.equalTo; import static org.hamcrest.junit.MatcherAssert.assertThat; @@ -68,7 +68,7 @@ private InternalNode createNode() Map props = new HashMap<>(); props.put( "k1", value( 1 ) ); props.put( "k2", value( 2 ) ); - return new InternalNode( 42L, Collections.singletonList( "L" ), props ); + return new InternalNode( 42L, String.valueOf( 42L ), Collections.singletonList( "L" ), props ); } } diff --git a/driver/src/test/java/org/neo4j/driver/internal/InternalPathTest.java b/driver/src/test/java/org/neo4j/driver/internal/InternalPathTest.java index f8eec384b8..6587632c23 100644 --- a/driver/src/test/java/org/neo4j/driver/internal/InternalPathTest.java +++ b/driver/src/test/java/org/neo4j/driver/internal/InternalPathTest.java @@ -40,11 +40,11 @@ private InternalPath testPath() { return new InternalPath( new InternalNode( 1 ), - new InternalRelationship( -1, 1, 2, "KNOWS" ), + new InternalRelationship( -1, String.valueOf( -1 ), 1, String.valueOf( 1 ), 2, String.valueOf( 2 ), "KNOWS" ), new InternalNode( 2 ), - new InternalRelationship( -2, 3, 2, "KNOWS" ), + new InternalRelationship( -2, String.valueOf( -2 ), 3, String.valueOf( 3 ), 2, String.valueOf( 2 ), "KNOWS" ), new InternalNode( 3 ), - new InternalRelationship( -3, 3, 4, "KNOWS" ), + new InternalRelationship( -3, String.valueOf( -3 ), 3, String.valueOf( 3 ), 4, String.valueOf( 4 ), "KNOWS" ), new InternalNode( 4 ) ); } @@ -82,17 +82,17 @@ void shouldBeAbleToIterateOverPathAsSegments() MatcherAssert.assertThat( segments, equalTo( Arrays.asList( (Path.Segment) new InternalPath.SelfContainedSegment( new InternalNode( 1 ), - new InternalRelationship( -1, 1, 2, "KNOWS" ), + new InternalRelationship( -1, String.valueOf( -1 ), 1, String.valueOf( 1 ), 2, String.valueOf( 2 ), "KNOWS" ), new InternalNode( 2 ) ), new InternalPath.SelfContainedSegment( new InternalNode( 2 ), - new InternalRelationship( -2, 3, 2, "KNOWS" ), + new InternalRelationship( -2, String.valueOf( -2 ), 3, String.valueOf( 3 ), 2, String.valueOf( 2 ), "KNOWS" ), new InternalNode( 3 ) ), new InternalPath.SelfContainedSegment( new InternalNode( 3 ), - new InternalRelationship( -3, 3, 4, "KNOWS" ), + new InternalRelationship( -3, String.valueOf( -3 ), 3, String.valueOf( 3 ), 4, String.valueOf( 4 ), "KNOWS" ), new InternalNode( 4 ) ) ) @@ -127,9 +127,12 @@ void shouldBeAbleToIterateOverPathRelationships() // Then assertThat( segments, equalTo( Arrays.asList( (Relationship) - new InternalRelationship( -1, 1, 2, "KNOWS" ), - new InternalRelationship( -2, 3, 2, "KNOWS" ), - new InternalRelationship( -3, 3, 4, "KNOWS" ) ) ) ); + new InternalRelationship( -1, String.valueOf( -1 ), 1, String.valueOf( 1 ), 2, + String.valueOf( 2 ), "KNOWS" ), + new InternalRelationship( -2, String.valueOf( -2 ), 3, String.valueOf( 3 ), 2, String.valueOf( 2 ), + "KNOWS" ), + new InternalRelationship( -3, String.valueOf( -3 ), 3, String.valueOf( 3 ), 4, String.valueOf( 4 ), + "KNOWS" ) ) ) ); } @Test @@ -142,9 +145,9 @@ void shouldNotBeAbleToCreatePathWithNoEntities() void shouldNotBeAbleToCreatePathWithEvenNumberOfEntities() { assertThrows( IllegalArgumentException.class, - () -> new InternalPath( - new InternalNode( 1 ), - new InternalRelationship( 2, 3, 4, "KNOWS" ) ) ); + () -> new InternalPath( + new InternalNode( 1 ), + new InternalRelationship( 2, String.valueOf( 2 ), 3, String.valueOf( 3 ), 4, String.valueOf( 4 ), "KNOWS" ) ) ); } @Test @@ -160,7 +163,7 @@ void shouldNotBeAbleToCreatePathWithNodeThatDoesNotConnect() assertThrows( IllegalArgumentException.class, () -> new InternalPath( new InternalNode( 1 ), - new InternalRelationship( 2, 1, 3, "KNOWS" ), + new InternalRelationship( 2, String.valueOf( 2 ), 1, String.valueOf( 1 ), 3, String.valueOf( 3 ), "KNOWS" ), new InternalNode( 4 ) ) ); } @@ -170,7 +173,7 @@ void shouldNotBeAbleToCreatePathWithRelationshipThatDoesNotConnect() assertThrows( IllegalArgumentException.class, () -> new InternalPath( new InternalNode( 1 ), - new InternalRelationship( 2, 3, 4, "KNOWS" ), + new InternalRelationship( 2, String.valueOf( 2 ), 3, String.valueOf( 3 ), 4, String.valueOf( 4 ), "KNOWS" ), new InternalNode( 3 ) ) ); } diff --git a/driver/src/test/java/org/neo4j/driver/internal/InternalRelationshipTest.java b/driver/src/test/java/org/neo4j/driver/internal/InternalRelationshipTest.java index 2de449e061..ed540782db 100644 --- a/driver/src/test/java/org/neo4j/driver/internal/InternalRelationshipTest.java +++ b/driver/src/test/java/org/neo4j/driver/internal/InternalRelationshipTest.java @@ -23,9 +23,9 @@ import java.util.HashMap; import java.util.Iterator; import java.util.Map; +import java.util.function.Function; import org.neo4j.driver.Value; -import java.util.function.Function; import static org.hamcrest.CoreMatchers.equalTo; import static org.hamcrest.junit.MatcherAssert.assertThat; @@ -68,7 +68,7 @@ private InternalRelationship createRelationship() props.put( "k1", value( 1 ) ); props.put( "k2", value( 2 ) ); - return new InternalRelationship(1L, 0L, 1L, "T", props ); + return new InternalRelationship( 1L, String.valueOf( 1L ), 0L, String.valueOf( 0L ), 1L, String.valueOf( 1L ), "T", props ); } } diff --git a/driver/src/test/java/org/neo4j/driver/internal/SelfContainedNodeTest.java b/driver/src/test/java/org/neo4j/driver/internal/SelfContainedNodeTest.java index eadb3a3c66..eca38360a6 100644 --- a/driver/src/test/java/org/neo4j/driver/internal/SelfContainedNodeTest.java +++ b/driver/src/test/java/org/neo4j/driver/internal/SelfContainedNodeTest.java @@ -22,8 +22,8 @@ import java.util.List; -import org.neo4j.driver.internal.util.Iterables; import org.neo4j.driver.Values; +import org.neo4j.driver.internal.util.Iterables; import org.neo4j.driver.types.Node; import static java.util.Collections.singletonList; @@ -36,8 +36,8 @@ class SelfContainedNodeTest { private Node adamTheNode() { - return new InternalNode( 1, singletonList( "Person" ), - parameters( "name", Values.value( "Adam" ) ).asMap( ofValue()) ); + return new InternalNode( 1, String.valueOf( 1 ), singletonList( "Person" ), + parameters( "name", Values.value( "Adam" ) ).asMap( ofValue() ) ); } @Test diff --git a/driver/src/test/java/org/neo4j/driver/internal/async/connection/BoltProtocolUtilTest.java b/driver/src/test/java/org/neo4j/driver/internal/async/connection/BoltProtocolUtilTest.java index eb4a22c00b..96093c4faa 100644 --- a/driver/src/test/java/org/neo4j/driver/internal/async/connection/BoltProtocolUtilTest.java +++ b/driver/src/test/java/org/neo4j/driver/internal/async/connection/BoltProtocolUtilTest.java @@ -23,9 +23,9 @@ import org.junit.jupiter.api.Test; import org.neo4j.driver.internal.messaging.v3.BoltProtocolV3; -import org.neo4j.driver.internal.messaging.v4.BoltProtocolV4; import org.neo4j.driver.internal.messaging.v41.BoltProtocolV41; import org.neo4j.driver.internal.messaging.v44.BoltProtocolV44; +import org.neo4j.driver.internal.messaging.v5.BoltProtocolV5; import static org.junit.jupiter.api.Assertions.assertEquals; import static org.neo4j.driver.internal.async.connection.BoltProtocolUtil.BOLT_MAGIC_PREAMBLE; @@ -43,15 +43,15 @@ void shouldReturnHandshakeBuf() { assertByteBufContains( handshakeBuf(), - BOLT_MAGIC_PREAMBLE, (2 << 16) | BoltProtocolV44.VERSION.toInt(), BoltProtocolV41.VERSION.toInt(), - BoltProtocolV4.VERSION.toInt(), BoltProtocolV3.VERSION.toInt() + BOLT_MAGIC_PREAMBLE, BoltProtocolV5.VERSION.toInt(), (2 << 16) | BoltProtocolV44.VERSION.toInt(), BoltProtocolV41.VERSION.toInt(), + BoltProtocolV3.VERSION.toInt() ); } @Test void shouldReturnHandshakeString() { - assertEquals( "[0x6060b017, 132100, 260, 4, 3]", handshakeString() ); + assertEquals( "[0x6060b017, 5, 132100, 260, 3]", handshakeString() ); } @Test 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 4e316797a8..43ffef62d4 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 @@ -67,7 +67,7 @@ void setUp() { channel = new EmbeddedChannel(); messageDispatcher = new InboundMessageDispatcher( channel, DEV_NULL_LOGGING ); - writer = new MessageToByteBufWriter( new KnowledgeableMessageFormat() ); + writer = new MessageToByteBufWriter( new KnowledgeableMessageFormat( false ) ); ChannelAttributes.setMessageDispatcher( channel, messageDispatcher ); InboundMessageHandler handler = new InboundMessageHandler( new MessageFormatV3(), DEV_NULL_LOGGING ); 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 109045f867..5a14935c02 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 @@ -23,7 +23,6 @@ import io.netty.channel.embedded.EmbeddedChannel; import org.junit.jupiter.api.Test; -import java.io.IOException; import java.util.HashMap; import java.util.List; @@ -125,7 +124,7 @@ private void assertSerializesValue( Value value ) throws Throwable private void assertSerializes( Message message ) throws Throwable { - EmbeddedChannel channel = newEmbeddedChannel( new KnowledgeableMessageFormat() ); + EmbeddedChannel channel = newEmbeddedChannel( new KnowledgeableMessageFormat( false ) ); ByteBuf packed = pack( message, channel ); Message unpackedMessage = unpack( packed, channel ); @@ -188,15 +187,15 @@ private void assertOnlyDeserializesValue( Value value ) throws Throwable assertEquals( message, unpackedMessage ); } - private ByteBuf knowledgeablePack( Message message ) throws IOException + private ByteBuf knowledgeablePack( Message message ) { - EmbeddedChannel channel = newEmbeddedChannel( new KnowledgeableMessageFormat() ); + EmbeddedChannel channel = newEmbeddedChannel( new KnowledgeableMessageFormat( false ) ); assertTrue( channel.writeOutbound( message ) ); ByteBuf[] packedMessages = channel.outboundMessages() - .stream() - .map( msg -> (ByteBuf) msg ) - .toArray( ByteBuf[]::new ); + .stream() + .map( msg -> (ByteBuf) msg ) + .toArray( ByteBuf[]::new ); return Unpooled.wrappedBuffer( packedMessages ); } diff --git a/driver/src/test/java/org/neo4j/driver/internal/messaging/v5/BoltProtocolV5Test.java b/driver/src/test/java/org/neo4j/driver/internal/messaging/v5/BoltProtocolV5Test.java new file mode 100644 index 0000000000..eac7d06528 --- /dev/null +++ b/driver/src/test/java/org/neo4j/driver/internal/messaging/v5/BoltProtocolV5Test.java @@ -0,0 +1,541 @@ +/* + * Copyright (c) "Neo4j" + * Neo4j Sweden AB [http://neo4j.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.v5; + +import io.netty.channel.ChannelPromise; +import io.netty.channel.embedded.EmbeddedChannel; +import org.junit.jupiter.api.AfterEach; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.params.ParameterizedTest; +import org.junit.jupiter.params.provider.EnumSource; +import org.mockito.ArgumentCaptor; + +import java.util.HashMap; +import java.util.Map; +import java.util.concurrent.CompletableFuture; +import java.util.concurrent.CompletionStage; + +import org.neo4j.driver.AccessMode; +import org.neo4j.driver.AuthTokens; +import org.neo4j.driver.Bookmark; +import org.neo4j.driver.Logging; +import org.neo4j.driver.Query; +import org.neo4j.driver.TransactionConfig; +import org.neo4j.driver.Value; +import org.neo4j.driver.internal.BookmarkHolder; +import org.neo4j.driver.internal.DatabaseName; +import org.neo4j.driver.internal.DefaultBookmarkHolder; +import org.neo4j.driver.internal.InternalBookmark; +import org.neo4j.driver.internal.async.UnmanagedTransaction; +import org.neo4j.driver.internal.async.connection.ChannelAttributes; +import org.neo4j.driver.internal.async.inbound.InboundMessageDispatcher; +import org.neo4j.driver.internal.cluster.RoutingContext; +import org.neo4j.driver.internal.cursor.AsyncResultCursor; +import org.neo4j.driver.internal.cursor.ResultCursorFactory; +import org.neo4j.driver.internal.handlers.BeginTxResponseHandler; +import org.neo4j.driver.internal.handlers.CommitTxResponseHandler; +import org.neo4j.driver.internal.handlers.PullAllResponseHandler; +import org.neo4j.driver.internal.handlers.RollbackTxResponseHandler; +import org.neo4j.driver.internal.handlers.RunResponseHandler; +import org.neo4j.driver.internal.messaging.BoltProtocol; +import org.neo4j.driver.internal.messaging.MessageFormat; +import org.neo4j.driver.internal.messaging.request.BeginMessage; +import org.neo4j.driver.internal.messaging.request.CommitMessage; +import org.neo4j.driver.internal.messaging.request.GoodbyeMessage; +import org.neo4j.driver.internal.messaging.request.HelloMessage; +import org.neo4j.driver.internal.messaging.request.PullMessage; +import org.neo4j.driver.internal.messaging.request.RollbackMessage; +import org.neo4j.driver.internal.messaging.request.RunWithMetadataMessage; +import org.neo4j.driver.internal.security.InternalAuthToken; +import org.neo4j.driver.internal.spi.Connection; +import org.neo4j.driver.internal.spi.ResponseHandler; + +import static java.time.Duration.ofSeconds; +import static java.util.Collections.emptyMap; +import static java.util.Collections.singletonMap; +import static org.hamcrest.Matchers.hasSize; +import static org.hamcrest.Matchers.instanceOf; +import static org.hamcrest.junit.MatcherAssert.assertThat; +import static org.junit.jupiter.api.Assertions.assertDoesNotThrow; +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertFalse; +import static org.junit.jupiter.api.Assertions.assertNotNull; +import static org.junit.jupiter.api.Assertions.assertNull; +import static org.junit.jupiter.api.Assertions.assertSame; +import static org.junit.jupiter.api.Assertions.assertThrows; +import static org.junit.jupiter.api.Assertions.assertTrue; +import static org.mockito.ArgumentMatchers.any; +import static org.mockito.ArgumentMatchers.eq; +import static org.mockito.Mockito.doAnswer; +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.verify; +import static org.mockito.Mockito.when; +import static org.neo4j.driver.AccessMode.WRITE; +import static org.neo4j.driver.Values.value; +import static org.neo4j.driver.internal.DatabaseNameUtil.database; +import static org.neo4j.driver.internal.DatabaseNameUtil.defaultDatabase; +import static org.neo4j.driver.internal.handlers.pulln.FetchSizeUtil.UNLIMITED_FETCH_SIZE; +import static org.neo4j.driver.util.TestUtil.await; +import static org.neo4j.driver.util.TestUtil.connectionMock; + +public class BoltProtocolV5Test +{ + protected static final String QUERY_TEXT = "RETURN $x"; + protected static final Map PARAMS = singletonMap( "x", value( 42 ) ); + protected static final Query QUERY = new Query( QUERY_TEXT, value( PARAMS ) ); + + protected final BoltProtocol protocol = createProtocol(); + private final EmbeddedChannel channel = new EmbeddedChannel(); + private final InboundMessageDispatcher messageDispatcher = new InboundMessageDispatcher( channel, Logging.none() ); + + private final TransactionConfig txConfig = TransactionConfig.builder() + .withTimeout( ofSeconds( 12 ) ) + .withMetadata( singletonMap( "key", value( 42 ) ) ) + .build(); + + protected BoltProtocol createProtocol() + { + return BoltProtocolV5.INSTANCE; + } + + @BeforeEach + void beforeEach() + { + ChannelAttributes.setMessageDispatcher( channel, messageDispatcher ); + } + + @AfterEach + void afterEach() + { + channel.finishAndReleaseAll(); + } + + @Test + void shouldCreateMessageFormat() + { + assertThat( protocol.createMessageFormat(), instanceOf( expectedMessageFormatType() ) ); + } + + @Test + void shouldInitializeChannel() + { + ChannelPromise promise = channel.newPromise(); + + protocol.initializeChannel( "MyDriver/0.0.1", dummyAuthToken(), RoutingContext.EMPTY, promise ); + + assertThat( channel.outboundMessages(), hasSize( 1 ) ); + assertThat( channel.outboundMessages().poll(), instanceOf( HelloMessage.class ) ); + assertEquals( 1, messageDispatcher.queuedHandlersCount() ); + assertFalse( promise.isDone() ); + + Map metadata = new HashMap<>(); + metadata.put( "server", value( "Neo4j/4.4.0" ) ); + metadata.put( "connection_id", value( "bolt-42" ) ); + + messageDispatcher.handleSuccessMessage( metadata ); + + assertTrue( promise.isDone() ); + assertTrue( promise.isSuccess() ); + } + + @Test + void shouldPrepareToCloseChannel() + { + protocol.prepareToCloseChannel( channel ); + + assertThat( channel.outboundMessages(), hasSize( 1 ) ); + assertThat( channel.outboundMessages().poll(), instanceOf( GoodbyeMessage.class ) ); + assertEquals( 1, messageDispatcher.queuedHandlersCount() ); + } + + @Test + void shouldFailToInitializeChannelWhenErrorIsReceived() + { + ChannelPromise promise = channel.newPromise(); + + protocol.initializeChannel( "MyDriver/2.2.1", dummyAuthToken(), RoutingContext.EMPTY, promise ); + + assertThat( channel.outboundMessages(), hasSize( 1 ) ); + assertThat( channel.outboundMessages().poll(), instanceOf( HelloMessage.class ) ); + assertEquals( 1, messageDispatcher.queuedHandlersCount() ); + assertFalse( promise.isDone() ); + + messageDispatcher.handleFailureMessage( "Neo.TransientError.General.DatabaseUnavailable", "Error!" ); + + assertTrue( promise.isDone() ); + assertFalse( promise.isSuccess() ); + } + + @Test + void shouldBeginTransactionWithoutBookmark() + { + Connection connection = connectionMock( protocol ); + + CompletionStage stage = protocol.beginTransaction( connection, InternalBookmark.empty(), TransactionConfig.empty() ); + + verify( connection ) + .writeAndFlush( eq( new BeginMessage( InternalBookmark.empty(), TransactionConfig.empty(), defaultDatabase(), WRITE, null ) ), + any( BeginTxResponseHandler.class ) ); + assertNull( await( stage ) ); + } + + @Test + void shouldBeginTransactionWithBookmarks() + { + Connection connection = connectionMock( protocol ); + Bookmark bookmark = InternalBookmark.parse( "neo4j:bookmark:v1:tx100" ); + + CompletionStage stage = protocol.beginTransaction( connection, bookmark, TransactionConfig.empty() ); + + verify( connection ) + .writeAndFlush( eq( new BeginMessage( bookmark, TransactionConfig.empty(), defaultDatabase(), WRITE, null ) ), + any( BeginTxResponseHandler.class ) ); + assertNull( await( stage ) ); + } + + @Test + void shouldBeginTransactionWithConfig() + { + Connection connection = connectionMock( protocol ); + + CompletionStage stage = protocol.beginTransaction( connection, InternalBookmark.empty(), txConfig ); + + verify( connection ) + .writeAndFlush( eq( new BeginMessage( InternalBookmark.empty(), txConfig, defaultDatabase(), WRITE, null ) ), + any( BeginTxResponseHandler.class ) ); + assertNull( await( stage ) ); + } + + @Test + void shouldBeginTransactionWithBookmarksAndConfig() + { + Connection connection = connectionMock( protocol ); + Bookmark bookmark = InternalBookmark.parse( "neo4j:bookmark:v1:tx4242" ); + + CompletionStage stage = protocol.beginTransaction( connection, bookmark, txConfig ); + + verify( connection ).writeAndFlush( eq( new BeginMessage( bookmark, txConfig, defaultDatabase(), WRITE, null ) ), any( BeginTxResponseHandler.class ) ); + assertNull( await( stage ) ); + } + + @Test + void shouldCommitTransaction() + { + String bookmarkString = "neo4j:bookmark:v1:tx4242"; + + Connection connection = connectionMock( protocol ); + when( connection.protocol() ).thenReturn( protocol ); + doAnswer( invocation -> + { + ResponseHandler commitHandler = invocation.getArgument( 1 ); + commitHandler.onSuccess( singletonMap( "bookmark", value( bookmarkString ) ) ); + return null; + } ).when( connection ).writeAndFlush( eq( CommitMessage.COMMIT ), any() ); + + CompletionStage stage = protocol.commitTransaction( connection ); + + verify( connection ).writeAndFlush( eq( CommitMessage.COMMIT ), any( CommitTxResponseHandler.class ) ); + assertEquals( InternalBookmark.parse( bookmarkString ), await( stage ) ); + } + + @Test + void shouldRollbackTransaction() + { + Connection connection = connectionMock( protocol ); + + CompletionStage stage = protocol.rollbackTransaction( connection ); + + verify( connection ).writeAndFlush( eq( RollbackMessage.ROLLBACK ), any( RollbackTxResponseHandler.class ) ); + assertNull( await( stage ) ); + } + + @ParameterizedTest + @EnumSource( AccessMode.class ) + void shouldRunInAutoCommitTransactionAndWaitForRunResponse( AccessMode mode ) throws Exception + { + testRunAndWaitForRunResponse( true, TransactionConfig.empty(), mode ); + } + + @ParameterizedTest + @EnumSource( AccessMode.class ) + void shouldRunInAutoCommitWithConfigTransactionAndWaitForRunResponse( AccessMode mode ) throws Exception + { + testRunAndWaitForRunResponse( true, txConfig, mode ); + } + + @ParameterizedTest + @EnumSource( AccessMode.class ) + void shouldRunInAutoCommitTransactionAndWaitForSuccessRunResponse( AccessMode mode ) throws Exception + { + testSuccessfulRunInAutoCommitTxWithWaitingForResponse( InternalBookmark.empty(), TransactionConfig.empty(), mode ); + } + + @ParameterizedTest + @EnumSource( AccessMode.class ) + void shouldRunInAutoCommitTransactionWithBookmarkAndConfigAndWaitForSuccessRunResponse( AccessMode mode ) throws Exception + { + testSuccessfulRunInAutoCommitTxWithWaitingForResponse( InternalBookmark.parse( "neo4j:bookmark:v1:tx65" ), txConfig, mode ); + } + + @ParameterizedTest + @EnumSource( AccessMode.class ) + void shouldRunInAutoCommitTransactionAndWaitForFailureRunResponse( AccessMode mode ) throws Exception + { + testFailedRunInAutoCommitTxWithWaitingForResponse( InternalBookmark.empty(), TransactionConfig.empty(), mode ); + } + + @ParameterizedTest + @EnumSource( AccessMode.class ) + void shouldRunInAutoCommitTransactionWithBookmarkAndConfigAndWaitForFailureRunResponse( AccessMode mode ) throws Exception + { + testFailedRunInAutoCommitTxWithWaitingForResponse( InternalBookmark.parse( "neo4j:bookmark:v1:tx163" ), txConfig, mode ); + } + + @ParameterizedTest + @EnumSource( AccessMode.class ) + void shouldRunInUnmanagedTransactionAndWaitForRunResponse( AccessMode mode ) throws Exception + { + testRunAndWaitForRunResponse( false, TransactionConfig.empty(), mode ); + } + + @ParameterizedTest + @EnumSource( AccessMode.class ) + void shouldRunInUnmanagedTransactionAndWaitForSuccessRunResponse( AccessMode mode ) throws Exception + { + testRunInUnmanagedTransactionAndWaitForRunResponse( true, mode ); + } + + @ParameterizedTest + @EnumSource( AccessMode.class ) + void shouldRunInUnmanagedTransactionAndWaitForFailureRunResponse( AccessMode mode ) throws Exception + { + testRunInUnmanagedTransactionAndWaitForRunResponse( false, mode ); + } + + @Test + void databaseNameInBeginTransaction() + { + testDatabaseNameSupport( false ); + } + + @Test + void databaseNameForAutoCommitTransactions() + { + testDatabaseNameSupport( true ); + } + + @Test + void shouldSupportDatabaseNameInBeginTransaction() + { + CompletionStage txStage = protocol.beginTransaction( connectionMock( "foo", protocol ), InternalBookmark.empty(), TransactionConfig.empty() ); + + assertDoesNotThrow( () -> await( txStage ) ); + } + + @Test + void shouldNotSupportDatabaseNameForAutoCommitTransactions() + { + assertDoesNotThrow( + () -> protocol.runInAutoCommitTransaction( connectionMock( "foo", protocol ), + new Query( "RETURN 1" ), BookmarkHolder.NO_OP, TransactionConfig.empty(), UNLIMITED_FETCH_SIZE ) ); + } + + private Class expectedMessageFormatType() + { + return MessageFormatV5.class; + } + + private void testFailedRunInAutoCommitTxWithWaitingForResponse( Bookmark bookmark, TransactionConfig config, AccessMode mode ) throws Exception + { + // Given + Connection connection = connectionMock( mode, protocol ); + BookmarkHolder bookmarkHolder = new DefaultBookmarkHolder( bookmark ); + + CompletableFuture cursorFuture = + protocol.runInAutoCommitTransaction( connection, QUERY, bookmarkHolder, config, UNLIMITED_FETCH_SIZE ) + .asyncResult() + .toCompletableFuture(); + + ResponseHandler runHandler = verifySessionRunInvoked( connection, bookmark, config, mode, defaultDatabase() ); + assertFalse( cursorFuture.isDone() ); + + // When I response to Run message with a failure + Throwable error = new RuntimeException(); + runHandler.onFailure( error ); + + // Then + assertEquals( bookmark, bookmarkHolder.getBookmark() ); + assertTrue( cursorFuture.isDone() ); + Throwable actual = assertThrows( error.getClass(), () -> await( cursorFuture.get().mapSuccessfulRunCompletionAsync() ) ); + assertSame( error, actual ); + } + + private void testSuccessfulRunInAutoCommitTxWithWaitingForResponse( Bookmark bookmark, TransactionConfig config, AccessMode mode ) throws Exception + { + // Given + Connection connection = connectionMock( mode, protocol ); + BookmarkHolder bookmarkHolder = new DefaultBookmarkHolder( bookmark ); + + CompletableFuture cursorFuture = + protocol.runInAutoCommitTransaction( connection, QUERY, bookmarkHolder, config, UNLIMITED_FETCH_SIZE ) + .asyncResult() + .toCompletableFuture(); + + ResponseHandler runHandler = verifySessionRunInvoked( connection, bookmark, config, mode, defaultDatabase() ); + assertFalse( cursorFuture.isDone() ); + + // When I response to the run message + runHandler.onSuccess( emptyMap() ); + + // Then + assertEquals( bookmark, bookmarkHolder.getBookmark() ); + assertTrue( cursorFuture.isDone() ); + assertNotNull( cursorFuture.get() ); + } + + private void testRunInUnmanagedTransactionAndWaitForRunResponse( boolean success, AccessMode mode ) throws Exception + { + // Given + Connection connection = connectionMock( mode, protocol ); + + CompletableFuture cursorFuture = + protocol.runInUnmanagedTransaction( connection, QUERY, mock( UnmanagedTransaction.class ), UNLIMITED_FETCH_SIZE ) + .asyncResult() + .toCompletableFuture(); + + ResponseHandler runHandler = verifyTxRunInvoked( connection ); + assertFalse( cursorFuture.isDone() ); + Throwable error = new RuntimeException(); + + if ( success ) + { + runHandler.onSuccess( emptyMap() ); + } + else + { + // When responded with a failure + runHandler.onFailure( error ); + } + + // Then + assertTrue( cursorFuture.isDone() ); + if ( success ) + { + assertNotNull( await( cursorFuture.get().mapSuccessfulRunCompletionAsync() ) ); + } + else + { + Throwable actual = assertThrows( error.getClass(), () -> await( cursorFuture.get().mapSuccessfulRunCompletionAsync() ) ); + assertSame( error, actual ); + } + } + + private void testRunAndWaitForRunResponse( boolean autoCommitTx, TransactionConfig config, AccessMode mode ) throws Exception + { + // Given + Connection connection = connectionMock( mode, protocol ); + Bookmark initialBookmark = InternalBookmark.parse( "neo4j:bookmark:v1:tx987" ); + + CompletionStage cursorStage; + if ( autoCommitTx ) + { + BookmarkHolder bookmarkHolder = new DefaultBookmarkHolder( initialBookmark ); + cursorStage = protocol.runInAutoCommitTransaction( connection, QUERY, bookmarkHolder, config, UNLIMITED_FETCH_SIZE ) + .asyncResult(); + } + else + { + cursorStage = protocol.runInUnmanagedTransaction( connection, QUERY, mock( UnmanagedTransaction.class ), UNLIMITED_FETCH_SIZE ) + .asyncResult(); + } + + // When & Then + CompletableFuture cursorFuture = cursorStage.toCompletableFuture(); + assertFalse( cursorFuture.isDone() ); + + ResponseHandler runResponseHandler = + autoCommitTx ? verifySessionRunInvoked( connection, initialBookmark, config, mode, defaultDatabase() ) : verifyTxRunInvoked( connection ); + runResponseHandler.onSuccess( emptyMap() ); + + assertTrue( cursorFuture.isDone() ); + assertNotNull( cursorFuture.get() ); + } + + private void testDatabaseNameSupport( boolean autoCommitTx ) + { + Connection connection = connectionMock( "foo", protocol ); + if ( autoCommitTx ) + { + ResultCursorFactory factory = + protocol.runInAutoCommitTransaction( connection, QUERY, BookmarkHolder.NO_OP, TransactionConfig.empty(), UNLIMITED_FETCH_SIZE ); + CompletionStage resultStage = factory.asyncResult(); + ResponseHandler runHandler = + verifySessionRunInvoked( connection, InternalBookmark.empty(), TransactionConfig.empty(), AccessMode.WRITE, database( "foo" ) ); + runHandler.onSuccess( emptyMap() ); + await( resultStage ); + verifySessionRunInvoked( connection, InternalBookmark.empty(), TransactionConfig.empty(), AccessMode.WRITE, database( "foo" ) ); + } + else + { + CompletionStage txStage = protocol.beginTransaction( connection, InternalBookmark.empty(), TransactionConfig.empty() ); + await( txStage ); + verifyBeginInvoked( connection, InternalBookmark.empty(), TransactionConfig.empty(), AccessMode.WRITE, database( "foo" ) ); + } + } + + private ResponseHandler verifyTxRunInvoked( Connection connection ) + { + return verifyRunInvoked( connection, RunWithMetadataMessage.unmanagedTxRunMessage( QUERY ) ); + } + + private ResponseHandler verifySessionRunInvoked( Connection connection, Bookmark bookmark, TransactionConfig config, AccessMode mode, + DatabaseName databaseName ) + { + RunWithMetadataMessage runMessage = RunWithMetadataMessage.autoCommitTxRunMessage( QUERY, config, databaseName, mode, bookmark, null ); + return verifyRunInvoked( connection, runMessage ); + } + + private ResponseHandler verifyRunInvoked( Connection connection, RunWithMetadataMessage runMessage ) + { + ArgumentCaptor runHandlerCaptor = ArgumentCaptor.forClass( ResponseHandler.class ); + ArgumentCaptor pullHandlerCaptor = ArgumentCaptor.forClass( ResponseHandler.class ); + + verify( connection ).write( eq( runMessage ), runHandlerCaptor.capture() ); + verify( connection ).writeAndFlush( any( PullMessage.class ), pullHandlerCaptor.capture() ); + + assertThat( runHandlerCaptor.getValue(), instanceOf( RunResponseHandler.class ) ); + assertThat( pullHandlerCaptor.getValue(), instanceOf( PullAllResponseHandler.class ) ); + + return runHandlerCaptor.getValue(); + } + + private void verifyBeginInvoked( Connection connection, Bookmark bookmark, TransactionConfig config, AccessMode mode, DatabaseName databaseName ) + { + ArgumentCaptor beginHandlerCaptor = ArgumentCaptor.forClass( ResponseHandler.class ); + BeginMessage beginMessage = new BeginMessage( bookmark, config, databaseName, mode, null ); + verify( connection ).writeAndFlush( eq( beginMessage ), beginHandlerCaptor.capture() ); + assertThat( beginHandlerCaptor.getValue(), instanceOf( BeginTxResponseHandler.class ) ); + } + + private static InternalAuthToken dummyAuthToken() + { + return (InternalAuthToken) AuthTokens.basic( "hello", "world" ); + } +} diff --git a/driver/src/test/java/org/neo4j/driver/internal/messaging/v5/MessageFormatV5Test.java b/driver/src/test/java/org/neo4j/driver/internal/messaging/v5/MessageFormatV5Test.java new file mode 100644 index 0000000000..7967ac43ec --- /dev/null +++ b/driver/src/test/java/org/neo4j/driver/internal/messaging/v5/MessageFormatV5Test.java @@ -0,0 +1,50 @@ +/* + * Copyright (c) "Neo4j" + * Neo4j Sweden AB [http://neo4j.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.v5; + +import org.junit.jupiter.api.Test; + +import org.neo4j.driver.internal.messaging.MessageFormat; +import org.neo4j.driver.internal.packstream.PackInput; +import org.neo4j.driver.internal.packstream.PackOutput; + +import static org.hamcrest.MatcherAssert.assertThat; +import static org.hamcrest.Matchers.instanceOf; +import static org.mockito.Mockito.mock; + +public class MessageFormatV5Test +{ + private static final MessageFormat format = BoltProtocolV5.INSTANCE.createMessageFormat(); + + @Test + void shouldCreateCorrectWriter() + { + MessageFormat.Writer writer = format.newWriter( mock( PackOutput.class ) ); + + assertThat( writer, instanceOf( MessageWriterV5.class ) ); + } + + @Test + void shouldCreateCorrectReader() + { + MessageFormat.Reader reader = format.newReader( mock( PackInput.class ) ); + + assertThat( reader, instanceOf( MessageReaderV5.class ) ); + } +} diff --git a/driver/src/test/java/org/neo4j/driver/internal/messaging/v5/MessageReaderV5Test.java b/driver/src/test/java/org/neo4j/driver/internal/messaging/v5/MessageReaderV5Test.java new file mode 100644 index 0000000000..cd118c87c9 --- /dev/null +++ b/driver/src/test/java/org/neo4j/driver/internal/messaging/v5/MessageReaderV5Test.java @@ -0,0 +1,123 @@ +/* + * Copyright (c) "Neo4j" + * Neo4j Sweden AB [http://neo4j.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.v5; + +import java.time.LocalDate; +import java.time.LocalDateTime; +import java.time.LocalTime; +import java.time.OffsetTime; +import java.time.ZoneId; +import java.time.ZoneOffset; +import java.time.ZonedDateTime; +import java.util.HashMap; +import java.util.stream.Stream; + +import org.neo4j.driver.Value; +import org.neo4j.driver.Values; +import org.neo4j.driver.internal.InternalPoint2D; +import org.neo4j.driver.internal.InternalPoint3D; +import org.neo4j.driver.internal.messaging.Message; +import org.neo4j.driver.internal.messaging.MessageFormat; +import org.neo4j.driver.internal.messaging.request.DiscardAllMessage; +import org.neo4j.driver.internal.messaging.response.FailureMessage; +import org.neo4j.driver.internal.messaging.response.IgnoredMessage; +import org.neo4j.driver.internal.messaging.response.RecordMessage; +import org.neo4j.driver.internal.messaging.response.SuccessMessage; +import org.neo4j.driver.internal.packstream.PackInput; +import org.neo4j.driver.internal.util.messaging.AbstractMessageReaderTestBase; + +import static java.util.Arrays.asList; +import static java.util.Calendar.APRIL; +import static java.util.Calendar.AUGUST; +import static org.neo4j.driver.Values.parameters; +import static org.neo4j.driver.Values.value; +import static org.neo4j.driver.internal.util.ValueFactory.emptyNodeValue; +import static org.neo4j.driver.internal.util.ValueFactory.emptyPathValue; +import static org.neo4j.driver.internal.util.ValueFactory.emptyRelationshipValue; +import static org.neo4j.driver.internal.util.ValueFactory.filledNodeValue; +import static org.neo4j.driver.internal.util.ValueFactory.filledPathValue; +import static org.neo4j.driver.internal.util.ValueFactory.filledRelationshipValue; + +/** + * The MessageReader under tests is the one provided by the {@link BoltProtocolV5} and not a specific class implementation. + *

+ * It's done on this way to make easy to replace the implementation and still getting the same behaviour. + */ +public class MessageReaderV5Test extends AbstractMessageReaderTestBase +{ + @Override + protected Stream supportedMessages() + { + return Stream.of( + // V2 Record types + record( value( new InternalPoint2D( 42, 120.65, -99.2 ) ) ), + record( value( new InternalPoint3D( 42, 85.391, 98.8, 11.1 ) ) ), + record( value( LocalDate.of( 2012, AUGUST, 3 ) ) ), + record( value( OffsetTime.of( 23, 59, 59, 999, ZoneOffset.MAX ) ) ), + record( value( LocalTime.of( 12, 25 ) ) ), + record( value( LocalDateTime.of( 1999, APRIL, 3, 19, 5, 5, 100_200_300 ) ) ), + record( value( ZonedDateTime.of( 1823, 1, 12, 23, 59, 59, 999_999_999, ZoneOffset.ofHoursMinutes( -7, -15 ) ) ) ), + record( value( ZonedDateTime.of( 1823, 1, 12, 23, 59, 59, 999_999_999, ZoneId.of( "Europe/Stockholm" ) ) ) ), + record( value( Values.isoDuration( Long.MAX_VALUE - 1, Integer.MAX_VALUE - 1, Short.MAX_VALUE - 1, Byte.MAX_VALUE - 1 ).asIsoDuration() ) ), + record( value( Values.isoDuration( 17, 22, 99, 15 ).asIsoDuration() ) ), + + // Bolt previous versions valid messages + new FailureMessage( "Hello", "World!" ), + IgnoredMessage.IGNORED, + new SuccessMessage( new HashMap<>() ), + record( value( 1337L ) ), + record( value( parameters( "cat", null, "dog", null ) ) ), + record( value( parameters( "k", 12, "a", "banana" ) ) ), + record( value( asList( "k", 12, "a", "banana" ) ) ), + + // V3 Record Types + record( emptyNodeValue() ), + record( filledNodeValue() ), + record( emptyRelationshipValue() ), + record( filledRelationshipValue() ), + record( filledPathValue() ), + record( emptyPathValue() ) + ); + } + + @Override + protected Stream unsupportedMessages() + { + return Stream.of( + DiscardAllMessage.DISCARD_ALL + ); + } + + @Override + protected MessageFormat.Reader newReader( PackInput input ) + { + return BoltProtocolV5.INSTANCE.createMessageFormat().newReader( input ); + } + + private Message record( Value value ) + { + return new RecordMessage( new Value[]{value} ); + } + + @Override + protected boolean isElementIdEnabled() + { + return true; + } +} diff --git a/driver/src/test/java/org/neo4j/driver/internal/messaging/v5/MessageWriterV5Test.java b/driver/src/test/java/org/neo4j/driver/internal/messaging/v5/MessageWriterV5Test.java new file mode 100644 index 0000000000..ea233fee4c --- /dev/null +++ b/driver/src/test/java/org/neo4j/driver/internal/messaging/v5/MessageWriterV5Test.java @@ -0,0 +1,148 @@ +/* + * Copyright (c) "Neo4j" + * Neo4j Sweden AB [http://neo4j.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.v5; + +import java.time.LocalDate; +import java.time.LocalDateTime; +import java.time.LocalTime; +import java.time.OffsetTime; +import java.time.ZoneId; +import java.time.ZoneOffset; +import java.time.ZonedDateTime; +import java.util.Collections; +import java.util.HashMap; +import java.util.Map; +import java.util.stream.Stream; + +import org.neo4j.driver.Query; +import org.neo4j.driver.Value; +import org.neo4j.driver.Values; +import org.neo4j.driver.internal.InternalBookmark; +import org.neo4j.driver.internal.messaging.Message; +import org.neo4j.driver.internal.messaging.MessageFormat; +import org.neo4j.driver.internal.messaging.request.BeginMessage; +import org.neo4j.driver.internal.messaging.request.DiscardMessage; +import org.neo4j.driver.internal.messaging.request.HelloMessage; +import org.neo4j.driver.internal.messaging.request.PullMessage; +import org.neo4j.driver.internal.messaging.request.RouteMessage; +import org.neo4j.driver.internal.packstream.PackOutput; +import org.neo4j.driver.internal.security.InternalAuthToken; +import org.neo4j.driver.internal.util.messaging.AbstractMessageWriterTestBase; + +import static java.time.Duration.ofSeconds; +import static java.util.Calendar.DECEMBER; +import static java.util.Collections.emptyMap; +import static java.util.Collections.singletonMap; +import static org.neo4j.driver.AccessMode.READ; +import static org.neo4j.driver.AccessMode.WRITE; +import static org.neo4j.driver.AuthTokens.basic; +import static org.neo4j.driver.Values.point; +import static org.neo4j.driver.Values.value; +import static org.neo4j.driver.internal.DatabaseNameUtil.database; +import static org.neo4j.driver.internal.DatabaseNameUtil.defaultDatabase; +import static org.neo4j.driver.internal.messaging.request.CommitMessage.COMMIT; +import static org.neo4j.driver.internal.messaging.request.DiscardAllMessage.DISCARD_ALL; +import static org.neo4j.driver.internal.messaging.request.GoodbyeMessage.GOODBYE; +import static org.neo4j.driver.internal.messaging.request.PullAllMessage.PULL_ALL; +import static org.neo4j.driver.internal.messaging.request.ResetMessage.RESET; +import static org.neo4j.driver.internal.messaging.request.RollbackMessage.ROLLBACK; +import static org.neo4j.driver.internal.messaging.request.RunWithMetadataMessage.autoCommitTxRunMessage; +import static org.neo4j.driver.internal.messaging.request.RunWithMetadataMessage.unmanagedTxRunMessage; + +/** + * The MessageWriter under tests is the one provided by the {@link BoltProtocolV5} and not a specific class implementation. + *

+ * It's done on this way to make easy to replace the implementation and still getting the same behaviour. + */ +public class MessageWriterV5Test extends AbstractMessageWriterTestBase +{ + @Override + protected MessageFormat.Writer newWriter( PackOutput output ) + { + return BoltProtocolV5.INSTANCE.createMessageFormat().newWriter( output ); + } + + @Override + protected Stream supportedMessages() + { + return Stream.of( + // Bolt V2 Data Types + unmanagedTxRunMessage( new Query( "RETURN $point", singletonMap( "point", point( 42, 12.99, -180.0 ) ) ) ), + unmanagedTxRunMessage( new Query( "RETURN $point", singletonMap( "point", point( 42, 0.51, 2.99, 100.123 ) ) ) ), + unmanagedTxRunMessage( new Query( "RETURN $date", singletonMap( "date", value( LocalDate.ofEpochDay( 2147483650L ) ) ) ) ), + unmanagedTxRunMessage( new Query( "RETURN $time", singletonMap( "time", value( OffsetTime.of( 4, 16, 20, 999, ZoneOffset.MIN ) ) ) ) ), + unmanagedTxRunMessage( new Query( "RETURN $time", singletonMap( "time", value( LocalTime.of( 12, 9, 18, 999_888 ) ) ) ) ), + unmanagedTxRunMessage( + new Query( "RETURN $dateTime", singletonMap( "dateTime", value( LocalDateTime.of( 2049, DECEMBER, 12, 17, 25, 49, 199 ) ) ) ) ), + unmanagedTxRunMessage( new Query( "RETURN $dateTime", singletonMap( "dateTime", value( ZonedDateTime.of( 2000, 1, 10, 12, 2, 49, 300, ZoneOffset + .ofHoursMinutes( 9, 30 ) ) ) ) ) ), + unmanagedTxRunMessage( new Query( "RETURN $dateTime", singletonMap( "dateTime", value( ZonedDateTime.of( 2000, 1, 10, 12, 2, 49, 300, ZoneId.of( + "Europe/Stockholm" ) ) ) ) ) ), + + // New Bolt V4 messages + new PullMessage( 100, 200 ), + new DiscardMessage( 300, 400 ), + + // Bolt V3 messages + new HelloMessage( "MyDriver/1.2.3", ((InternalAuthToken) basic( "neo4j", "neo4j" )).toMap(), Collections.emptyMap() ), + GOODBYE, + new BeginMessage( InternalBookmark.parse( "neo4j:bookmark:v1:tx123" ), ofSeconds( 5 ), singletonMap( "key", value( 42 ) ), READ, + defaultDatabase(), null ), + new BeginMessage( InternalBookmark.parse( "neo4j:bookmark:v1:tx123" ), ofSeconds( 5 ), singletonMap( "key", value( 42 ) ), WRITE, + database( "foo" ), null ), + COMMIT, + ROLLBACK, + + RESET, + autoCommitTxRunMessage( new Query( "RETURN 1" ), ofSeconds( 5 ), singletonMap( "key", value( 42 ) ), defaultDatabase(), READ, + InternalBookmark.parse( "neo4j:bookmark:v1:tx1" ), null ), + autoCommitTxRunMessage( new Query( "RETURN 1" ), ofSeconds( 5 ), singletonMap( "key", value( 42 ) ), database( "foo" ), WRITE, + InternalBookmark.parse( "neo4j:bookmark:v1:tx1" ), null ), + unmanagedTxRunMessage( new Query( "RETURN 1" ) ), + + // Bolt V3 messages with struct values + autoCommitTxRunMessage( new Query( "RETURN $x", singletonMap( "x", value( ZonedDateTime.now() ) ) ), ofSeconds( 1 ), emptyMap(), + defaultDatabase(), READ, InternalBookmark.empty(), null ), + autoCommitTxRunMessage( new Query( "RETURN $x", singletonMap( "x", value( ZonedDateTime.now() ) ) ), ofSeconds( 1 ), emptyMap(), + database( "foo" ), + WRITE, InternalBookmark.empty(), null ), + unmanagedTxRunMessage( new Query( "RETURN $x", singletonMap( "x", point( 42, 1, 2, 3 ) ) ) ), + + // New 4.3 Messages + routeMessage() + ); + } + + @Override + protected Stream unsupportedMessages() + { + return Stream.of( + // Bolt V1, V2 and V3 messages + PULL_ALL, + DISCARD_ALL + ); + } + + private RouteMessage routeMessage() + { + Map routeContext = new HashMap<>(); + routeContext.put( "someContext", Values.value( 124 ) ); + return new RouteMessage( routeContext, InternalBookmark.empty(), "dbName", null ); + } +} diff --git a/driver/src/test/java/org/neo4j/driver/internal/util/ValueFactory.java b/driver/src/test/java/org/neo4j/driver/internal/util/ValueFactory.java index 38d27bc076..21c988033c 100644 --- a/driver/src/test/java/org/neo4j/driver/internal/util/ValueFactory.java +++ b/driver/src/test/java/org/neo4j/driver/internal/util/ValueFactory.java @@ -26,7 +26,6 @@ import org.neo4j.driver.internal.value.NodeValue; import org.neo4j.driver.internal.value.PathValue; import org.neo4j.driver.internal.value.RelationshipValue; -import org.neo4j.driver.Value; import static java.util.Collections.singletonList; import static java.util.Collections.singletonMap; @@ -36,27 +35,32 @@ public class ValueFactory { public static NodeValue emptyNodeValue() { - return new NodeValue( new InternalNode( 1234, singletonList( "User" ), new HashMap() ) ); + return new NodeValue( new InternalNode( 1234, String.valueOf( 1234 ), singletonList( "User" ), new HashMap<>() ) ); } public static NodeValue filledNodeValue() { - return new NodeValue( new InternalNode( 1234, singletonList( "User" ), singletonMap( "name", value( "Dodo" ) ) ) ); + return new NodeValue( new InternalNode( 1234, String.valueOf( 1234 ), singletonList( "User" ), singletonMap( "name", value( "Dodo" ) ) ) ); } public static RelationshipValue emptyRelationshipValue() { - return new RelationshipValue( new InternalRelationship( 1234, 1, 2, "KNOWS" ) ); + return new RelationshipValue( new InternalRelationship( 1234, String.valueOf( 1234 ), 1, String.valueOf( 1 ), 2, String.valueOf( 2 ), "KNOWS" ) ); } public static RelationshipValue filledRelationshipValue() { - return new RelationshipValue( new InternalRelationship( 1234, 1, 2, "KNOWS", singletonMap( "name", value( "Dodo" ) ) ) ); + return new RelationshipValue( + new InternalRelationship( 1234, String.valueOf( 1234 ), 1, String.valueOf( 1 ), 2, String.valueOf( 2 ), "KNOWS", + singletonMap( "name", value( "Dodo" ) ) ) ); } public static PathValue filledPathValue() { - return new PathValue( new InternalPath( new InternalNode(42L), new InternalRelationship( 43L, 42L, 44L, "T" ), new InternalNode( 44L ) ) ); + return new PathValue( + new InternalPath( new InternalNode( 42L ), + new InternalRelationship( 43L, String.valueOf( 43L ), 42L, String.valueOf( 42L ), 44L, String.valueOf( 44L ), "T" ), + new InternalNode( 44L ) ) ); } public static PathValue emptyPathValue() diff --git a/driver/src/test/java/org/neo4j/driver/internal/util/messaging/AbstractMessageReaderTestBase.java b/driver/src/test/java/org/neo4j/driver/internal/util/messaging/AbstractMessageReaderTestBase.java index 259c30a72a..306f90501d 100644 --- a/driver/src/test/java/org/neo4j/driver/internal/util/messaging/AbstractMessageReaderTestBase.java +++ b/driver/src/test/java/org/neo4j/driver/internal/util/messaging/AbstractMessageReaderTestBase.java @@ -88,7 +88,7 @@ Stream shouldFailToReadUnsupportedMessages() dynamicTest( message.toString(), () -> testUnsupportedMessageReading( message ) ) ); } - private void testUnsupportedMessageReading( Message message ) throws IOException + private void testUnsupportedMessageReading( Message message ) { assertThrows( IOException.class, () -> testMessageReading( message ) ); } @@ -110,11 +110,11 @@ protected ResponseMessageHandler testMessageReading( Message message ) throws IO return handler; } - private static PackInput newInputWith( Message message ) throws IOException + private PackInput newInputWith( Message message ) throws IOException { ByteBuf buffer = Unpooled.buffer(); - MessageFormat messageFormat = new KnowledgeableMessageFormat(); + MessageFormat messageFormat = new KnowledgeableMessageFormat( isElementIdEnabled() ); MessageFormat.Writer writer = messageFormat.newWriter( new ByteBufOutput( buffer ) ); writer.write( message ); @@ -122,4 +122,9 @@ private static PackInput newInputWith( Message message ) throws IOException input.start( buffer ); return input; } + + protected boolean isElementIdEnabled() + { + return false; + } } diff --git a/driver/src/test/java/org/neo4j/driver/internal/util/messaging/KnowledgeableMessageFormat.java b/driver/src/test/java/org/neo4j/driver/internal/util/messaging/KnowledgeableMessageFormat.java index e53df8bc99..8ac1399c65 100644 --- a/driver/src/test/java/org/neo4j/driver/internal/util/messaging/KnowledgeableMessageFormat.java +++ b/driver/src/test/java/org/neo4j/driver/internal/util/messaging/KnowledgeableMessageFormat.java @@ -51,17 +51,24 @@ */ public class KnowledgeableMessageFormat extends MessageFormatV3 { + private final boolean elementIdEnabled; + + public KnowledgeableMessageFormat( boolean elementIdEnabled ) + { + this.elementIdEnabled = elementIdEnabled; + } + @Override public Writer newWriter( PackOutput output ) { - return new KnowledgeableMessageWriter( output ); + return new KnowledgeableMessageWriter( output, elementIdEnabled ); } private static class KnowledgeableMessageWriter extends AbstractMessageWriter { - KnowledgeableMessageWriter( PackOutput output ) + KnowledgeableMessageWriter( PackOutput output, boolean enableElementId ) { - super( new KnowledgeableValuePacker( output ), buildEncoders() ); + super( new KnowledgeableValuePacker( output, enableElementId ), buildEncoders() ); } static Map buildEncoders() @@ -82,9 +89,12 @@ static Map buildEncoders() private static class KnowledgeableValuePacker extends CommonValuePacker { - KnowledgeableValuePacker( PackOutput output ) + private final boolean elementIdEnabled; + + KnowledgeableValuePacker( PackOutput output, boolean elementIdEnabled ) { super( output ); + this.elementIdEnabled = elementIdEnabled; } @Override @@ -143,10 +153,14 @@ private void packPath( Path path ) throws IOException packer.packListHeader( relIdx.size() ); for ( Relationship rel : relIdx.keySet() ) { - packer.packStructHeader( 3, CommonValueUnpacker.UNBOUND_RELATIONSHIP ); + packer.packStructHeader( elementIdEnabled ? 4 : 3, CommonValueUnpacker.UNBOUND_RELATIONSHIP ); packer.pack( rel.id() ); packer.pack( rel.type() ); packProperties( rel ); + if ( elementIdEnabled ) + { + packer.pack( rel.elementId() ); + } } // Sequence @@ -164,7 +178,7 @@ private void packPath( Path path ) throws IOException private void packRelationship( Relationship rel ) throws IOException { - packer.packStructHeader( 5, CommonValueUnpacker.RELATIONSHIP ); + packer.packStructHeader( elementIdEnabled ? 8 : 5, CommonValueUnpacker.RELATIONSHIP ); packer.pack( rel.id() ); packer.pack( rel.startNodeId() ); packer.pack( rel.endNodeId() ); @@ -172,11 +186,18 @@ private void packRelationship( Relationship rel ) throws IOException packer.pack( rel.type() ); packProperties( rel ); + + if ( elementIdEnabled ) + { + packer.pack( rel.elementId() ); + packer.pack( rel.startNodeElementId() ); + packer.pack( rel.endNodeElementId() ); + } } private void packNode( Node node ) throws IOException { - packer.packStructHeader( CommonValueUnpacker.NODE_FIELDS, CommonValueUnpacker.NODE ); + packer.packStructHeader( elementIdEnabled ? 4 : 3, CommonValueUnpacker.NODE ); packer.pack( node.id() ); Iterable labels = node.labels(); @@ -187,6 +208,11 @@ private void packNode( Node node ) throws IOException } packProperties( node ); + + if ( elementIdEnabled ) + { + packer.pack( node.elementId() ); + } } private void packProperties( Entity entity ) throws IOException diff --git a/driver/src/test/java/org/neo4j/driver/types/TypeSystemTest.java b/driver/src/test/java/org/neo4j/driver/types/TypeSystemTest.java index 48c8e83486..2ecf3dfcfb 100644 --- a/driver/src/test/java/org/neo4j/driver/types/TypeSystemTest.java +++ b/driver/src/test/java/org/neo4j/driver/types/TypeSystemTest.java @@ -28,6 +28,7 @@ import java.util.HashSet; import java.util.Set; +import org.neo4j.driver.Value; import org.neo4j.driver.internal.InternalNode; import org.neo4j.driver.internal.InternalPath; import org.neo4j.driver.internal.InternalRelationship; @@ -35,18 +36,18 @@ import org.neo4j.driver.internal.value.NodeValue; import org.neo4j.driver.internal.value.PathValue; import org.neo4j.driver.internal.value.RelationshipValue; -import org.neo4j.driver.Value; import static org.hamcrest.CoreMatchers.not; import static org.hamcrest.core.Is.is; import static org.hamcrest.junit.MatcherAssert.assertThat; -import static org.neo4j.driver.internal.types.InternalTypeSystem.TYPE_SYSTEM; import static org.neo4j.driver.Values.value; +import static org.neo4j.driver.internal.types.InternalTypeSystem.TYPE_SYSTEM; class TypeSystemTest { private final InternalNode node = new InternalNode( 42L ); - private final InternalRelationship relationship = new InternalRelationship( 42L, 42L, 43L, "T" ); + private final InternalRelationship relationship = + new InternalRelationship( 42L, String.valueOf( 42L ), 42L, String.valueOf( 42L ), 43L, String.valueOf( 43L ), "T" ); private Value integerValue = value( 13 ); private Value floatValue = value( 13.1 ); diff --git a/driver/src/test/java/org/neo4j/driver/util/TestUtil.java b/driver/src/test/java/org/neo4j/driver/util/TestUtil.java index b7498ac8ae..00390b3a50 100644 --- a/driver/src/test/java/org/neo4j/driver/util/TestUtil.java +++ b/driver/src/test/java/org/neo4j/driver/util/TestUtil.java @@ -76,6 +76,7 @@ import org.neo4j.driver.internal.messaging.v42.BoltProtocolV42; import org.neo4j.driver.internal.messaging.v43.BoltProtocolV43; import org.neo4j.driver.internal.messaging.v44.BoltProtocolV44; +import org.neo4j.driver.internal.messaging.v5.BoltProtocolV5; import org.neo4j.driver.internal.retry.RetryLogic; import org.neo4j.driver.internal.spi.Connection; import org.neo4j.driver.internal.spi.ConnectionProvider; @@ -512,7 +513,8 @@ public static Connection connectionMock( String databaseName, AccessMode mode, B BoltProtocolVersion version = protocol.version(); if ( version.equals( BoltProtocolV3.VERSION ) || version.equals( BoltProtocolV4.VERSION ) || version.equals( BoltProtocolV41.VERSION ) || version.equals( BoltProtocolV42.VERSION ) || - version.equals( BoltProtocolV43.VERSION ) || version.equals( BoltProtocolV44.VERSION ) ) + version.equals( BoltProtocolV43.VERSION ) || version.equals( BoltProtocolV44.VERSION ) || + version.equals( BoltProtocolV5.VERSION ) ) { setupSuccessResponse( connection, CommitMessage.class ); setupSuccessResponse( connection, RollbackMessage.class ); diff --git a/testkit-backend/src/main/java/neo4j/org/testkit/backend/messages/requests/GetFeatures.java b/testkit-backend/src/main/java/neo4j/org/testkit/backend/messages/requests/GetFeatures.java index 3f55e8eb84..dacb2c6e90 100644 --- a/testkit-backend/src/main/java/neo4j/org/testkit/backend/messages/requests/GetFeatures.java +++ b/testkit-backend/src/main/java/neo4j/org/testkit/backend/messages/requests/GetFeatures.java @@ -40,6 +40,7 @@ public class GetFeatures implements TestkitRequest "Feature:Bolt:4.2", "Feature:Bolt:4.3", "Feature:Bolt:4.4", + "Feature:Bolt:5.0", "AuthorizationExpiredTreatment", "ConfHint:connection.recv_timeout_seconds", "Temporary:DriverFetchSize", diff --git a/testkit-backend/src/main/java/neo4j/org/testkit/backend/messages/responses/serializer/TestkitNodeValueSerializer.java b/testkit-backend/src/main/java/neo4j/org/testkit/backend/messages/responses/serializer/TestkitNodeValueSerializer.java index 19dd829cf4..321662cab6 100644 --- a/testkit-backend/src/main/java/neo4j/org/testkit/backend/messages/responses/serializer/TestkitNodeValueSerializer.java +++ b/testkit-backend/src/main/java/neo4j/org/testkit/backend/messages/responses/serializer/TestkitNodeValueSerializer.java @@ -50,6 +50,7 @@ public void serialize( NodeValue nodeValue, JsonGenerator gen, SerializerProvide { Node node = nodeValue.asNode(); gen.writeObjectField( "id", new IntegerValue( node.id() ) ); + gen.writeObjectField( "elementId", new StringValue( node.elementId() ) ); StringValue[] labels = StreamSupport.stream( node.labels().spliterator(), false ) .map( StringValue::new ) @@ -57,7 +58,6 @@ public void serialize( NodeValue nodeValue, JsonGenerator gen, SerializerProvide gen.writeObjectField( "labels", new ListValue( labels ) ); gen.writeObjectField( "props", new MapValue( node.asMap( Function.identity() ) ) ); - } ); } } diff --git a/testkit-backend/src/main/java/neo4j/org/testkit/backend/messages/responses/serializer/TestkitRelationshipValueSerializer.java b/testkit-backend/src/main/java/neo4j/org/testkit/backend/messages/responses/serializer/TestkitRelationshipValueSerializer.java index 8bbbcc41ad..1acfd3d6eb 100644 --- a/testkit-backend/src/main/java/neo4j/org/testkit/backend/messages/responses/serializer/TestkitRelationshipValueSerializer.java +++ b/testkit-backend/src/main/java/neo4j/org/testkit/backend/messages/responses/serializer/TestkitRelationshipValueSerializer.java @@ -47,8 +47,11 @@ public void serialize( RelationshipValue relationshipValue, JsonGenerator gen, S { Relationship relationship = relationshipValue.asRelationship(); gen.writeObjectField( "id", new IntegerValue( relationship.id() ) ); + gen.writeObjectField( "elementId", new StringValue( relationship.elementId() ) ); gen.writeObjectField( "startNodeId", new IntegerValue( relationship.startNodeId() ) ); + gen.writeObjectField( "startNodeElementId", new StringValue( relationship.startNodeElementId() ) ); gen.writeObjectField( "endNodeId", new IntegerValue( relationship.endNodeId() ) ); + gen.writeObjectField( "endNodeElementId", new StringValue( relationship.endNodeElementId() ) ); gen.writeObjectField( "type", new StringValue( relationship.type() ) ); gen.writeObjectField( "props", new MapValue( relationship.asMap( Function.identity() ) ) ); } );