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 extends MessageFormat> 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() ) ) );
} );