From 776a1f0a86c44cc33d400db999e688167053dd8a Mon Sep 17 00:00:00 2001 From: Dmitriy Tverdiakov Date: Fri, 18 Mar 2022 23:19:50 +0000 Subject: [PATCH 1/2] Add exceptions to numeric id accessors in nodes and relationships Depending on server configuration numeric id might not be available and accessing it will result in `IllegalStateException`. --- .../neo4j/driver/internal/InternalEntity.java | 20 ++- .../neo4j/driver/internal/InternalNode.java | 11 +- .../driver/internal/InternalRelationship.java | 15 +- .../messaging/common/CommonValueUnpacker.java | 8 +- .../messaging/v5/ValueUnpackerV5.java | 40 +++-- .../internal/packstream/PackStream.java | 4 +- .../java/org/neo4j/driver/types/Entity.java | 5 +- .../org/neo4j/driver/types/Relationship.java | 4 + .../neo4j/driver/TransactionConfigTest.java | 2 +- .../neo4j/driver/internal/ExtractTest.java | 10 +- ...ternalMapAccessorWithDefaultValueTest.java | 22 +-- .../driver/internal/InternalNodeTest.java | 16 +- .../driver/internal/InternalPathTest.java | 27 ++-- .../internal/InternalRelationshipTest.java | 41 ++++- .../internal/SelfContainedNodeTest.java | 3 +- .../driver/internal/util/ValueFactory.java | 15 +- .../neo4j/driver/types/TypeSystemTest.java | 4 +- .../backend/messages/AbstractResultNext.java | 99 ++++++++++++ .../messages/requests/CypherTypeField.java | 151 ++++++++++++++++++ .../messages/requests/GetFeatures.java | 3 +- .../backend/messages/requests/ResultNext.java | 73 +-------- .../messages/requests/TestkitRequest.java | 3 +- .../backend/messages/responses/Field.java | 42 +++++ .../TestkitNodeValueSerializer.java | 15 +- .../TestkitRelationshipValueSerializer.java | 19 ++- 25 files changed, 500 insertions(+), 152 deletions(-) create mode 100644 testkit-backend/src/main/java/neo4j/org/testkit/backend/messages/AbstractResultNext.java create mode 100644 testkit-backend/src/main/java/neo4j/org/testkit/backend/messages/requests/CypherTypeField.java create mode 100644 testkit-backend/src/main/java/neo4j/org/testkit/backend/messages/responses/Field.java 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 ee719e6cda..3f5d30ed3a 100644 --- a/driver/src/main/java/org/neo4j/driver/internal/InternalEntity.java +++ b/driver/src/main/java/org/neo4j/driver/internal/InternalEntity.java @@ -32,20 +32,25 @@ public abstract class InternalEntity implements Entity, AsValue { + public static final String INVALID_ID_ERROR = "Numeric id is not available, please use string based element id alternative"; + private final long id; private final String elementId; private final Map properties; + private final boolean numericIdAvailable; - public InternalEntity( long id, String elementId, Map properties ) + public InternalEntity( long id, String elementId, Map properties, boolean numericIdAvailable ) { this.id = id; this.elementId = elementId; this.properties = properties; + this.numericIdAvailable = numericIdAvailable; } @Override public long id() { + assertNumericIdAvailable(); return id; } @@ -142,4 +147,17 @@ public Iterable values( Function mapFunction ) { return Iterables.map( properties.values(), mapFunction ); } + + protected void assertNumericIdAvailable() + { + if ( !numericIdAvailable ) + { + throw new IllegalStateException( INVALID_ID_ERROR ); + } + } + + public boolean isNumericIdAvailable() + { + return numericIdAvailable; + } } 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 f0c4a992c2..77fedad0af 100644 --- a/driver/src/main/java/org/neo4j/driver/internal/InternalNode.java +++ b/driver/src/main/java/org/neo4j/driver/internal/InternalNode.java @@ -35,12 +35,17 @@ public class InternalNode extends InternalEntity implements Node public InternalNode( long id ) { - this( id, String.valueOf( id ), Collections.emptyList(), Collections.emptyMap() ); + this( id, Collections.emptyList(), Collections.emptyMap() ); } - public InternalNode( long id, String elementId, Collection labels, Map properties ) + public InternalNode( long id, Collection labels, Map properties ) { - super( id, elementId, properties ); + this( id, String.valueOf( id ), labels, properties, true ); + } + + public InternalNode( long id, String elementId, Collection labels, Map properties, boolean numericIdAvailable ) + { + super( id, elementId, properties, numericIdAvailable ); 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 c256bf5c49..d14990a629 100644 --- a/driver/src/main/java/org/neo4j/driver/internal/InternalRelationship.java +++ b/driver/src/main/java/org/neo4j/driver/internal/InternalRelationship.java @@ -36,15 +36,20 @@ public class InternalRelationship extends InternalEntity implements Relationship private String endElementId; private final String type; - public InternalRelationship( long id, String elementId, long start, String startElementId, long end, String endElementId, String type ) + public InternalRelationship( long id, long start, long end, String type ) { - this( id, elementId, start, startElementId, end, endElementId, type, Collections.emptyMap() ); + this( id, start, end, type, Collections.emptyMap() ); + } + + public InternalRelationship( long id, long start, long end, String type, Map properties ) + { + this( id, String.valueOf( id ), start, String.valueOf( start ), end, String.valueOf( end ), type, properties, true ); } public InternalRelationship( long id, String elementId, long start, String startElementId, long end, String endElementId, String type, - Map properties ) + Map properties, boolean numericIdAvailable ) { - super( id, elementId, properties ); + super( id, elementId, properties, numericIdAvailable ); this.start = start; this.startElementId = startElementId; this.end = end; @@ -72,6 +77,7 @@ public void setStartAndEnd( long start, String startElementId, long end, String @Override public long startNodeId() { + assertNumericIdAvailable(); return start; } @@ -84,6 +90,7 @@ public String startNodeElementId() @Override public long endNodeId() { + assertNumericIdAvailable(); return end; } 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 928acfe8f3..7607f47815 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 @@ -237,8 +237,8 @@ protected Value unpackRelationship() throws IOException String relType = unpacker.unpackString(); Map props = unpackMap(); - InternalRelationship adapted = - new InternalRelationship( urn, String.valueOf( urn ), startUrn, String.valueOf( startUrn ), endUrn, String.valueOf( endUrn ), relType, props ); + InternalRelationship adapted = new InternalRelationship( urn, String.valueOf( urn ), startUrn, String.valueOf( startUrn ), endUrn, + String.valueOf( endUrn ), relType, props, true ); return new RelationshipValue( adapted ); } @@ -260,7 +260,7 @@ protected InternalNode unpackNode() throws IOException props.put( key, unpack() ); } - return new InternalNode( urn, String.valueOf( urn ), labels, props ); + return new InternalNode( urn, String.valueOf( urn ), labels, props, true ); } protected Value unpackPath() throws IOException @@ -283,7 +283,7 @@ protected Value unpackPath() throws IOException long id = unpacker.unpackLong(); String relType = unpacker.unpackString(); Map props = unpackMap(); - uniqRels[i] = new InternalRelationship( id, String.valueOf( id ), -1, String.valueOf( -1 ), -1, String.valueOf( -1 ), relType, props ); + uniqRels[i] = new InternalRelationship( id, String.valueOf( id ), -1, String.valueOf( -1 ), -1, String.valueOf( -1 ), relType, props, true ); } // Path sequence 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 index 8ac67f6b52..1b6b59b709 100644 --- 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 @@ -63,7 +63,7 @@ protected int getRelationshipFields() @Override protected InternalNode unpackNode() throws IOException { - long urn = unpacker.unpackLongOrDefaultOnNull( -1 ); + Long urn = unpacker.unpackLongOrNull(); int numLabels = (int) unpacker.unpackListHeader(); List labels = new ArrayList<>( numLabels ); @@ -81,14 +81,14 @@ protected InternalNode unpackNode() throws IOException String elementId = unpacker.unpackString(); - return new InternalNode( urn, elementId, labels, props ); + return urn == null ? new InternalNode( -1, elementId, labels, props, false ) : new InternalNode( urn, elementId, labels, props, true ); } @Override protected Value unpackPath() throws IOException { // List of unique nodes - Node[] uniqNodes = new Node[(int) unpacker.unpackListHeader()]; + InternalNode[] uniqNodes = new InternalNode[(int) unpacker.unpackListHeader()]; for ( int i = 0; i < uniqNodes.length; i++ ) { ensureCorrectStructSize( TypeConstructor.NODE, getNodeFields(), unpacker.unpackStructHeader() ); @@ -102,11 +102,13 @@ protected Value unpackPath() throws IOException { ensureCorrectStructSize( TypeConstructor.RELATIONSHIP, 4, unpacker.unpackStructHeader() ); ensureCorrectStructSignature( "UNBOUND_RELATIONSHIP", UNBOUND_RELATIONSHIP, unpacker.unpackStructSignature() ); - Long id = unpacker.unpackLongOrDefaultOnNull( -1 ); + Long id = unpacker.unpackLongOrNull(); 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 ); + uniqRels[i] = id == null + ? new InternalRelationship( -1, elementId, -1, String.valueOf( -1 ), -1, String.valueOf( -1 ), relType, props, false ) + : new InternalRelationship( id, elementId, -1, String.valueOf( -1 ), -1, String.valueOf( -1 ), relType, props, true ); } // Path sequence @@ -117,7 +119,7 @@ protected Value unpackPath() throws IOException 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 + InternalNode 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++ ) @@ -128,12 +130,12 @@ protected Value unpackPath() throws IOException if ( relIdx < 0 ) { rel = uniqRels[(-relIdx) - 1]; // -1 because rel idx are 1-indexed - rel.setStartAndEnd( nextNode.id(), nextNode.elementId(), prevNode.id(), prevNode.elementId() ); + setStartAndEnd( rel, nextNode, prevNode ); } else { rel = uniqRels[relIdx - 1]; - rel.setStartAndEnd( prevNode.id(), prevNode.elementId(), nextNode.id(), nextNode.elementId() ); + setStartAndEnd( rel, prevNode, nextNode ); } nodes[i + 1] = nextNode; @@ -144,19 +146,33 @@ protected Value unpackPath() throws IOException return new PathValue( new InternalPath( Arrays.asList( segments ), Arrays.asList( nodes ), Arrays.asList( rels ) ) ); } + private void setStartAndEnd( InternalRelationship rel, InternalNode start, InternalNode end ) + { + if ( rel.isNumericIdAvailable() && start.isNumericIdAvailable() && end.isNumericIdAvailable() ) + { + rel.setStartAndEnd( start.id(), start.elementId(), end.id(), end.elementId() ); + } + else + { + rel.setStartAndEnd( -1, start.elementId(), -1, end.elementId() ); + } + } + @Override protected Value unpackRelationship() throws IOException { - long urn = unpacker.unpackLongOrDefaultOnNull( -1 ); - long startUrn = unpacker.unpackLongOrDefaultOnNull( -1 ); - long endUrn = unpacker.unpackLongOrDefaultOnNull( -1 ); + Long urn = unpacker.unpackLongOrNull(); + Long startUrn = unpacker.unpackLongOrNull(); + Long endUrn = unpacker.unpackLongOrNull(); 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 ); + InternalRelationship adapted = urn == null + ? new InternalRelationship( -1, elementId, -1, startElementId, -1, endElementId, relType, props, false ) + : new InternalRelationship( urn, elementId, startUrn, startElementId, endUrn, endElementId, relType, props, true ); 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 77a306e631..eac75d9553 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 @@ -467,7 +467,7 @@ public long unpackMapHeader() throws IOException } } - public Long unpackLongOrDefaultOnNull( long defaultValue ) throws IOException + public Long unpackLongOrNull() throws IOException { final byte markerByte = in.readByte(); if ( markerByte >= MINUS_2_TO_THE_4 ) @@ -485,7 +485,7 @@ public Long unpackLongOrDefaultOnNull( long defaultValue ) throws IOException case INT_64: return in.readLong(); case NULL: - return defaultValue; + return null; 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 55ce7a4279..e076568009 100644 --- a/driver/src/main/java/org/neo4j/driver/types/Entity.java +++ b/driver/src/main/java/org/neo4j/driver/types/Entity.java @@ -31,9 +31,10 @@ 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. + *

+ * Please note that depending on server configuration numeric id might not be available and accessing it will result in {@link IllegalStateException}. * - * @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()} + * @return the id of this entity * @deprecated superseded by {@link #elementId()} */ @Deprecated 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 01a62196c6..36ae337c40 100644 --- a/driver/src/main/java/org/neo4j/driver/types/Relationship.java +++ b/driver/src/main/java/org/neo4j/driver/types/Relationship.java @@ -26,6 +26,8 @@ public interface Relationship extends Entity { /** * Id of the node where this relationship starts. + *

+ * Please note that depending on server configuration numeric id might not be available and accessing it will result in {@link IllegalStateException}. * * @return the node id * @deprecated superseded by {@link #startNodeElementId()} @@ -42,6 +44,8 @@ public interface Relationship extends Entity /** * Id of the node where this relationship ends. + *

+ * Please note that depending on server configuration numeric id might not be available and accessing it will result in {@link IllegalStateException}. * * @return the node id * @deprecated superseded by {@link #endNodeElementId()} diff --git a/driver/src/test/java/org/neo4j/driver/TransactionConfigTest.java b/driver/src/test/java/org/neo4j/driver/TransactionConfigTest.java index 2297d30428..aa011e7515 100644 --- a/driver/src/test/java/org/neo4j/driver/TransactionConfigTest.java +++ b/driver/src/test/java/org/neo4j/driver/TransactionConfigTest.java @@ -71,7 +71,7 @@ void shouldDisallowMetadataWithIllegalValues() assertThrows( ClientException.class, () -> TransactionConfig.builder().withMetadata( - singletonMap( "key", new InternalRelationship( 1, String.valueOf( 1 ), 1, String.valueOf( 1 ), 1, String.valueOf( 1 ), "" ) ) ) ); + singletonMap( "key", new InternalRelationship( 1, 1, 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 a996639d6c..e5c380c75f 100644 --- a/driver/src/test/java/org/neo4j/driver/internal/ExtractTest.java +++ b/driver/src/test/java/org/neo4j/driver/internal/ExtractTest.java @@ -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, String.valueOf( 42L ), Collections.singletonList( "L" ), props ); + InternalNode node = new InternalNode( 42L, Collections.singletonList( "L" ), props ); // WHEN Iterable> properties = Extract.properties( node, Value::asInt ); @@ -143,7 +143,7 @@ void testProperties() void testFields() { // GIVEN - InternalRecord record = new InternalRecord( Arrays.asList( "k1" ), new Value[]{value( 42 )} ); + InternalRecord record = new InternalRecord( singletonList( "k1" ), new Value[]{value( 42 )} ); // WHEN List> fields = Extract.fields( record, Value::asInt ); @@ -181,11 +181,7 @@ void shouldExtractMapOfValues() void shouldFailToExtractMapOfValuesFromUnsupportedValues() { assertThrows( ClientException.class, () -> Extract.mapOfValues( singletonMap( "key", new InternalNode( 1 ) ) ) ); - 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 InternalRelationship( 1, 1, 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 080803b66c..766b3c5c17 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, String.valueOf( 0L ), 0L, String.valueOf( 0L ), 1L, String.valueOf( 1L ), "T" ), + new InternalRelationship( 0L, 0L, 1L, "T" ), new InternalNode( 1L ) ) ); Value realPathValue = new PathValue( createPath() ); assertThat( record.get( "PathValue", defaultPathValue ), equalTo( realPathValue ) ); @@ -104,7 +104,7 @@ void shouldGetValueFromRecord() // Rel Value defaultRelValue = - new RelationshipValue( new InternalRelationship( 0L, String.valueOf( 0L ), 0L, String.valueOf( 0L ), 1L, String.valueOf( 1L ), "T" ) ); + new RelationshipValue( new InternalRelationship( 0L, 0L, 1L, "T" ) ); Value realRelValue = new RelationshipValue( createRel() ); assertThat( record.get( "RelValue", defaultRelValue ), equalTo( realRelValue ) ); assertThat( record.get( wrongKey, defaultRelValue ), equalTo( defaultRelValue ) ); @@ -136,11 +136,11 @@ void shouldGetEntityFromRecord() Record record = createRecord(); Entity defaultNodeEntity = new InternalNode( 0L ); - assertThat( record.get( "NodeValue", defaultNodeEntity ), equalTo( (Entity) createNode() ) ); + assertThat( record.get( "NodeValue", defaultNodeEntity ), equalTo( createNode() ) ); assertThat( record.get( wrongKey, defaultNodeEntity ), equalTo( defaultNodeEntity ) ); - 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() ) ); + Entity defaultRelEntity = new InternalRelationship( 0L, 0L, 1L, "T" ); + assertThat( record.get( "RelValue", defaultRelEntity ), equalTo( createRel() ) ); assertThat( record.get( wrongKey, defaultRelEntity ), equalTo( defaultRelEntity ) ); } @@ -159,7 +159,7 @@ void shouldGetRelFromRecord() { Record record = createRecord(); - Relationship defaultRel = new InternalRelationship( 0L, String.valueOf( 0L ), 0L, String.valueOf( 0L ), 1L, String.valueOf( 1L ), "T" ); + Relationship defaultRel = new InternalRelationship( 0L, 0L, 1L, "T" ); assertThat( record.get( "RelValue", defaultRel ), equalTo( createRel() ) ); assertThat( record.get( wrongKey, defaultRel ), equalTo( defaultRel ) ); } @@ -171,7 +171,7 @@ void shouldGetPathFromRecord() Path defaultPath = new InternalPath( new InternalNode( 0L ), - new InternalRelationship( 0L, String.valueOf( 0L ), 0L, String.valueOf( 0L ), 1L, String.valueOf( 1L ), "T" ), + new InternalRelationship( 0L, 0L, 1L, "T" ), new InternalNode( 1L ) ); assertThat( record.get( "PathValue", defaultPath ), equalTo( createPath() ) ); assertThat( record.get( wrongKey, defaultPath ), equalTo( defaultPath ) ); @@ -271,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, String.valueOf( 42L ), Collections.singletonList( "L" ), props ) ); + NodeValue nodeValue = new NodeValue( new InternalNode( 42L, Collections.singletonList( "L" ), props ) ); assertThat( nodeValue.get( "k1", 0 ), equalTo( 43 ) ); assertThat( nodeValue.get( "k2", "" ), equalTo( "hello world" ) ); @@ -285,7 +285,7 @@ void shouldGetFromRel() props.put( "k1", value( 43 ) ); props.put( "k2", value( "hello world" ) ); RelationshipValue relValue = - new RelationshipValue( new InternalRelationship( 0L, String.valueOf( 0L ), 0L, String.valueOf( 0L ), 1L, String.valueOf( 1L ), "T", props ) ); + new RelationshipValue( new InternalRelationship( 0L, 0L, 1L, "T", props ) ); assertThat( relValue.get( "k1", 0 ), equalTo( 43 ) ); assertThat( relValue.get( "k2", "" ), equalTo( "hello world" ) ); @@ -296,7 +296,7 @@ private Path createPath() { return new InternalPath( new InternalNode( 42L ), - new InternalRelationship( 43L, String.valueOf( 43L ), 42L, String.valueOf( 42L ), 44L, String.valueOf( 44L ), "T" ), + new InternalRelationship( 43L, 42L, 44L, "T" ), new InternalNode( 44L ) ); } @@ -307,7 +307,7 @@ private Node createNode() private Relationship createRel() { - return new InternalRelationship( 1L, String.valueOf( 1L ), 1L, String.valueOf( 1L ), 2L, String.valueOf( 2L ), "T" ); + return new InternalRelationship( 1L, 1L, 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 524b62f360..58ce8702f3 100644 --- a/driver/src/test/java/org/neo4j/driver/internal/InternalNodeTest.java +++ b/driver/src/test/java/org/neo4j/driver/internal/InternalNodeTest.java @@ -30,7 +30,9 @@ import static org.hamcrest.CoreMatchers.equalTo; import static org.hamcrest.junit.MatcherAssert.assertThat; +import static org.junit.jupiter.api.Assertions.assertEquals; import static org.junit.jupiter.api.Assertions.assertFalse; +import static org.junit.jupiter.api.Assertions.assertThrows; import static org.neo4j.driver.Values.NULL; import static org.neo4j.driver.Values.value; @@ -63,12 +65,22 @@ void accessUnknownKeyShouldBeNull() assertThat( node.get( "k3" ), equalTo( NULL ) ); } + @Test + void shouldThrowOnIdWhenNumericIdUnavailable() + { + // GIVEN + InternalNode node = new InternalNode( -1, "value", Collections.emptyList(), Collections.emptyMap(), false ); + + // WHEN & THEN + IllegalStateException e = assertThrows( IllegalStateException.class, node::id ); + assertEquals( InternalEntity.INVALID_ID_ERROR, e.getMessage() ); + } + private InternalNode createNode() { Map props = new HashMap<>(); props.put( "k1", value( 1 ) ); props.put( "k2", value( 2 ) ); - return new InternalNode( 42L, String.valueOf( 42L ), Collections.singletonList( "L" ), props ); + return new InternalNode( 42L, String.valueOf( 42L ), Collections.singletonList( "L" ), props, true ); } - } 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 6587632c23..7896c2129e 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, String.valueOf( -1 ), 1, String.valueOf( 1 ), 2, String.valueOf( 2 ), "KNOWS" ), + new InternalRelationship( -1, 1, 2, "KNOWS" ), new InternalNode( 2 ), - new InternalRelationship( -2, String.valueOf( -2 ), 3, String.valueOf( 3 ), 2, String.valueOf( 2 ), "KNOWS" ), + new InternalRelationship( -2, 3, 2, "KNOWS" ), new InternalNode( 3 ), - new InternalRelationship( -3, String.valueOf( -3 ), 3, String.valueOf( 3 ), 4, String.valueOf( 4 ), "KNOWS" ), + new InternalRelationship( -3, 3, 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, String.valueOf( -1 ), 1, String.valueOf( 1 ), 2, String.valueOf( 2 ), "KNOWS" ), + new InternalRelationship( -1, 1, 2, "KNOWS" ), new InternalNode( 2 ) ), new InternalPath.SelfContainedSegment( new InternalNode( 2 ), - new InternalRelationship( -2, String.valueOf( -2 ), 3, String.valueOf( 3 ), 2, String.valueOf( 2 ), "KNOWS" ), + new InternalRelationship( -2, 3, 2, "KNOWS" ), new InternalNode( 3 ) ), new InternalPath.SelfContainedSegment( new InternalNode( 3 ), - new InternalRelationship( -3, String.valueOf( -3 ), 3, String.valueOf( 3 ), 4, String.valueOf( 4 ), "KNOWS" ), + new InternalRelationship( -3, 3, 4, "KNOWS" ), new InternalNode( 4 ) ) ) @@ -127,12 +127,9 @@ void shouldBeAbleToIterateOverPathRelationships() // Then assertThat( segments, equalTo( Arrays.asList( (Relationship) - 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" ) ) ) ); + new InternalRelationship( -1, 1, 2, "KNOWS" ), + new InternalRelationship( -2, 3, 2, "KNOWS" ), + new InternalRelationship( -3, 3, 4, "KNOWS" ) ) ) ); } @Test @@ -147,7 +144,7 @@ void shouldNotBeAbleToCreatePathWithEvenNumberOfEntities() assertThrows( IllegalArgumentException.class, () -> new InternalPath( new InternalNode( 1 ), - new InternalRelationship( 2, String.valueOf( 2 ), 3, String.valueOf( 3 ), 4, String.valueOf( 4 ), "KNOWS" ) ) ); + new InternalRelationship( 2, 3, 4, "KNOWS" ) ) ); } @Test @@ -163,7 +160,7 @@ void shouldNotBeAbleToCreatePathWithNodeThatDoesNotConnect() assertThrows( IllegalArgumentException.class, () -> new InternalPath( new InternalNode( 1 ), - new InternalRelationship( 2, String.valueOf( 2 ), 1, String.valueOf( 1 ), 3, String.valueOf( 3 ), "KNOWS" ), + new InternalRelationship( 2, 1, 3, "KNOWS" ), new InternalNode( 4 ) ) ); } @@ -173,7 +170,7 @@ void shouldNotBeAbleToCreatePathWithRelationshipThatDoesNotConnect() assertThrows( IllegalArgumentException.class, () -> new InternalPath( new InternalNode( 1 ), - new InternalRelationship( 2, String.valueOf( 2 ), 3, String.valueOf( 3 ), 4, String.valueOf( 4 ), "KNOWS" ), + new InternalRelationship( 2, 3, 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 ed540782db..680296fd9c 100644 --- a/driver/src/test/java/org/neo4j/driver/internal/InternalRelationshipTest.java +++ b/driver/src/test/java/org/neo4j/driver/internal/InternalRelationshipTest.java @@ -20,6 +20,7 @@ import org.junit.jupiter.api.Test; +import java.util.Collections; import java.util.HashMap; import java.util.Iterator; import java.util.Map; @@ -29,7 +30,9 @@ import static org.hamcrest.CoreMatchers.equalTo; import static org.hamcrest.junit.MatcherAssert.assertThat; +import static org.junit.jupiter.api.Assertions.assertEquals; import static org.junit.jupiter.api.Assertions.assertFalse; +import static org.junit.jupiter.api.Assertions.assertThrows; import static org.neo4j.driver.Values.NULL; import static org.neo4j.driver.Values.value; @@ -62,13 +65,49 @@ void accessUnknownKeyShouldBeNull() assertThat( relationship.get( "k3" ), equalTo( NULL ) ); } + @Test + void shouldThrowOnIdWhenNumericIdUnavailable() + { + // GIVEN + InternalRelationship relationship = + new InternalRelationship( -1, "value", 1, String.valueOf( 1 ), 2, String.valueOf( 2 ), "T", Collections.emptyMap(), false ); + + // WHEN & THEN + IllegalStateException e = assertThrows( IllegalStateException.class, relationship::id ); + assertEquals( InternalEntity.INVALID_ID_ERROR, e.getMessage() ); + } + + @Test + void shouldThrowOnStartNodeIdWhenNumericIdUnavailable() + { + // GIVEN + InternalRelationship relationship = + new InternalRelationship( -1, "value", 1, String.valueOf( 1 ), 2, String.valueOf( 2 ), "T", Collections.emptyMap(), false ); + + // WHEN & THEN + IllegalStateException e = assertThrows( IllegalStateException.class, relationship::startNodeId ); + assertEquals( InternalEntity.INVALID_ID_ERROR, e.getMessage() ); + } + + @Test + void shouldThrowOnEndNodeIdWhenNumericIdUnavailable() + { + // GIVEN + InternalRelationship relationship = + new InternalRelationship( -1, "value", 1, String.valueOf( 1 ), 2, String.valueOf( 2 ), "T", Collections.emptyMap(), false ); + + // WHEN & THEN + IllegalStateException e = assertThrows( IllegalStateException.class, relationship::endNodeId ); + assertEquals( InternalEntity.INVALID_ID_ERROR, e.getMessage() ); + } + private InternalRelationship createRelationship() { Map props = new HashMap<>(); props.put( "k1", value( 1 ) ); props.put( "k2", value( 2 ) ); - return new InternalRelationship( 1L, String.valueOf( 1L ), 0L, String.valueOf( 0L ), 1L, String.valueOf( 1L ), "T", props ); + return new InternalRelationship( 1L, 0L, 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 eca38360a6..380b269ede 100644 --- a/driver/src/test/java/org/neo4j/driver/internal/SelfContainedNodeTest.java +++ b/driver/src/test/java/org/neo4j/driver/internal/SelfContainedNodeTest.java @@ -36,8 +36,7 @@ class SelfContainedNodeTest { private Node adamTheNode() { - return new InternalNode( 1, String.valueOf( 1 ), singletonList( "Person" ), - parameters( "name", Values.value( "Adam" ) ).asMap( ofValue() ) ); + return new InternalNode( 1, singletonList( "Person" ), parameters( "name", Values.value( "Adam" ) ).asMap( ofValue() ) ); } @Test 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 21c988033c..2dbb6ada89 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 @@ -35,32 +35,27 @@ public class ValueFactory { public static NodeValue emptyNodeValue() { - return new NodeValue( new InternalNode( 1234, String.valueOf( 1234 ), singletonList( "User" ), new HashMap<>() ) ); + return new NodeValue( new InternalNode( 1234, singletonList( "User" ), new HashMap<>() ) ); } public static NodeValue filledNodeValue() { - return new NodeValue( new InternalNode( 1234, String.valueOf( 1234 ), singletonList( "User" ), singletonMap( "name", value( "Dodo" ) ) ) ); + return new NodeValue( new InternalNode( 1234, singletonList( "User" ), singletonMap( "name", value( "Dodo" ) ) ) ); } public static RelationshipValue emptyRelationshipValue() { - return new RelationshipValue( new InternalRelationship( 1234, String.valueOf( 1234 ), 1, String.valueOf( 1 ), 2, String.valueOf( 2 ), "KNOWS" ) ); + return new RelationshipValue( new InternalRelationship( 1234, 1, 2, "KNOWS" ) ); } public static RelationshipValue filledRelationshipValue() { - return new RelationshipValue( - new InternalRelationship( 1234, String.valueOf( 1234 ), 1, String.valueOf( 1 ), 2, String.valueOf( 2 ), "KNOWS", - singletonMap( "name", value( "Dodo" ) ) ) ); + return new RelationshipValue( new InternalRelationship( 1234, 1, 2, "KNOWS", singletonMap( "name", value( "Dodo" ) ) ) ); } public static PathValue filledPathValue() { - 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 ) ) ); + return new PathValue( new InternalPath( new InternalNode( 42L ), new InternalRelationship( 43L, 42L, 44L, "T" ), new InternalNode( 44L ) ) ); } public static PathValue emptyPathValue() 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 2ecf3dfcfb..14cf3adac4 100644 --- a/driver/src/test/java/org/neo4j/driver/types/TypeSystemTest.java +++ b/driver/src/test/java/org/neo4j/driver/types/TypeSystemTest.java @@ -45,9 +45,9 @@ class TypeSystemTest { - private final InternalNode node = new InternalNode( 42L ); + private final InternalNode node = new InternalNode( 42L, String.valueOf( 42L ), Collections.emptyList(), Collections.emptyMap(), true ); private final InternalRelationship relationship = - new InternalRelationship( 42L, String.valueOf( 42L ), 42L, String.valueOf( 42L ), 43L, String.valueOf( 43L ), "T" ); + new InternalRelationship( 42L, String.valueOf( 42L ), 42L, String.valueOf( 42L ), 43L, String.valueOf( 43L ), "T", Collections.emptyMap(), true ); private Value integerValue = value( 13 ); private Value floatValue = value( 13.1 ); diff --git a/testkit-backend/src/main/java/neo4j/org/testkit/backend/messages/AbstractResultNext.java b/testkit-backend/src/main/java/neo4j/org/testkit/backend/messages/AbstractResultNext.java new file mode 100644 index 0000000000..dec6b96b6d --- /dev/null +++ b/testkit-backend/src/main/java/neo4j/org/testkit/backend/messages/AbstractResultNext.java @@ -0,0 +1,99 @@ +/* + * 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 neo4j.org.testkit.backend.messages; + +import neo4j.org.testkit.backend.RxBufferedSubscriber; +import neo4j.org.testkit.backend.TestkitState; +import neo4j.org.testkit.backend.holder.RxResultHolder; +import neo4j.org.testkit.backend.messages.requests.TestkitRequest; +import neo4j.org.testkit.backend.messages.responses.NullRecord; +import neo4j.org.testkit.backend.messages.responses.TestkitResponse; +import reactor.core.publisher.Mono; + +import java.util.concurrent.CompletionStage; + +import org.neo4j.driver.Record; +import org.neo4j.driver.Result; +import org.neo4j.driver.exceptions.NoSuchRecordException; + +public abstract class AbstractResultNext implements TestkitRequest +{ + @Override + public TestkitResponse process( TestkitState testkitState ) + { + try + { + Result result = testkitState.getResultHolder( getResultId() ).getResult(); + return createResponse( result.next() ); + } + catch ( NoSuchRecordException ignored ) + { + return NullRecord.builder().build(); + } + } + + @Override + public CompletionStage processAsync( TestkitState testkitState ) + { + return testkitState.getAsyncResultHolder( getResultId() ) + .thenCompose( resultCursorHolder -> resultCursorHolder.getResult().nextAsync() ) + .thenApply( this::createResponseNullSafe ); + } + + @Override + public Mono processRx( TestkitState testkitState ) + { + return testkitState.getRxResultHolder( getResultId() ) + .flatMap( resultHolder -> + { + RxBufferedSubscriber subscriber = + resultHolder.getSubscriber() + .orElseGet( () -> + { + RxBufferedSubscriber subscriberInstance = + new RxBufferedSubscriber<>( + getFetchSize( resultHolder ) ); + resultHolder.setSubscriber( subscriberInstance ); + resultHolder.getResult().records() + .subscribe( subscriberInstance ); + return subscriberInstance; + } ); + return subscriber.next() + .map( this::createResponse ) + .defaultIfEmpty( NullRecord.builder().build() ); + } ); + } + + protected abstract neo4j.org.testkit.backend.messages.responses.TestkitResponse createResponse( Record record ); + + protected abstract String getResultId(); + + private neo4j.org.testkit.backend.messages.responses.TestkitResponse createResponseNullSafe( Record record ) + { + return record != null ? createResponse( record ) : NullRecord.builder().build(); + } + + private long getFetchSize( RxResultHolder resultHolder ) + { + long fetchSize = resultHolder.getSessionHolder().getConfig() + .fetchSize() + .orElse( resultHolder.getSessionHolder().getDriverHolder().getConfig().fetchSize() ); + return fetchSize == -1 ? Long.MAX_VALUE : fetchSize; + } +} diff --git a/testkit-backend/src/main/java/neo4j/org/testkit/backend/messages/requests/CypherTypeField.java b/testkit-backend/src/main/java/neo4j/org/testkit/backend/messages/requests/CypherTypeField.java new file mode 100644 index 0000000000..12410413b5 --- /dev/null +++ b/testkit-backend/src/main/java/neo4j/org/testkit/backend/messages/requests/CypherTypeField.java @@ -0,0 +1,151 @@ +/* + * 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 neo4j.org.testkit.backend.messages.requests; + +import lombok.Getter; +import lombok.Setter; +import neo4j.org.testkit.backend.CustomDriverError; +import neo4j.org.testkit.backend.messages.AbstractResultNext; +import neo4j.org.testkit.backend.messages.responses.BackendError; +import neo4j.org.testkit.backend.messages.responses.Field; +import neo4j.org.testkit.backend.messages.responses.TestkitResponse; + +import java.lang.reflect.InvocationTargetException; +import java.util.function.Supplier; +import java.util.stream.StreamSupport; + +import org.neo4j.driver.Record; +import org.neo4j.driver.Value; +import org.neo4j.driver.types.Entity; +import org.neo4j.driver.types.Path; + +@Setter +@Getter +public class CypherTypeField extends AbstractResultNext +{ + private CypherTypeFieldBody data; + + @Override + protected neo4j.org.testkit.backend.messages.responses.TestkitResponse createResponse( Record record ) + { + Value value = record.get( data.getRecordKey() ); + String type = data.getType(); + String field = data.getField(); + String fieldValue = null; + TestkitResponse testkitResponse = null; + + try + { + if ( "path".equals( type ) ) + { + fieldValue = readPath( value.asPath() ); + } + else if ( "node".equals( type ) ) + { + fieldValue = readProperty( value.asNode(), field ); + } + else if ( "relationship".equals( type ) ) + { + fieldValue = readProperty( value.asRelationship(), field ); + } + else + { + testkitResponse = BackendError.builder() + .data( BackendError.BackendErrorBody.builder().msg( String.format( "Unexpected type %s", type ) ).build() ) + .build(); + } + } + catch ( Throwable t ) + { + throw new CustomDriverError( t ); + } + + if ( testkitResponse == null ) + { + testkitResponse = neo4j.org.testkit.backend.messages.responses.Field.builder() + .data( Field.FieldBody.builder() + .value( fieldValue ) + .build() ) + .build(); + } + + return testkitResponse; + } + + @Override + protected String getResultId() + { + return data.getResultId(); + } + + private String readPath( Path path ) throws Throwable + { + String[] parts = data.getField().split( "\\." ); + String propertyName = parts[0]; + int index = Integer.parseInt( parts[1] ); + String methodName = parts[2]; + + Supplier> iterableSupplier; + if ( "nodes".equals( propertyName ) ) + { + iterableSupplier = path::nodes; + } + else if ( "relationships".equals( propertyName ) ) + { + iterableSupplier = path::relationships; + } + else + { + throw new RuntimeException( "Unexpected" ); + } + + Entity entity = getEntity( iterableSupplier.get(), index ); + return readProperty( entity, methodName ); + } + + private Entity getEntity( Iterable iterable, int index ) + { + return StreamSupport.stream( iterable.spliterator(), false ) + .skip( index > 0 ? index - 1 : index ) + .findFirst() + .orElseThrow( IndexOutOfBoundsException::new ); + } + + private String readProperty( Entity value, String property ) throws Throwable + { + try + { + return String.valueOf( value.getClass().getMethod( property ).invoke( value ) ); + } + catch ( InvocationTargetException e ) + { + throw e.getTargetException(); + } + } + + @Setter + @Getter + public static class CypherTypeFieldBody + { + private String resultId; + private String recordKey; + private String type; + private String field; + } +} 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 dacb2c6e90..6e5b877423 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 @@ -65,7 +65,8 @@ public class GetFeatures implements TestkitRequest "Feature:API:ConnectionAcquisitionTimeout", "Feature:API:Driver.IsEncrypted", "Feature:API:SSLConfig", - "Detail:DefaultSecurityConfigValueEquality" + "Detail:DefaultSecurityConfigValueEquality", + "Detail:ThrowOnMissingId" ) ); private static final Set SYNC_FEATURES = new HashSet<>( Arrays.asList( diff --git a/testkit-backend/src/main/java/neo4j/org/testkit/backend/messages/requests/ResultNext.java b/testkit-backend/src/main/java/neo4j/org/testkit/backend/messages/requests/ResultNext.java index 6a434ca3d5..659683d904 100644 --- a/testkit-backend/src/main/java/neo4j/org/testkit/backend/messages/requests/ResultNext.java +++ b/testkit-backend/src/main/java/neo4j/org/testkit/backend/messages/requests/ResultNext.java @@ -20,80 +20,18 @@ import lombok.Getter; import lombok.Setter; -import neo4j.org.testkit.backend.RxBufferedSubscriber; -import neo4j.org.testkit.backend.TestkitState; -import neo4j.org.testkit.backend.holder.RxResultHolder; -import neo4j.org.testkit.backend.messages.responses.NullRecord; -import neo4j.org.testkit.backend.messages.responses.TestkitResponse; -import reactor.core.publisher.Mono; - -import java.util.concurrent.CompletionStage; +import neo4j.org.testkit.backend.messages.AbstractResultNext; import org.neo4j.driver.Record; -import org.neo4j.driver.Result; -import org.neo4j.driver.exceptions.NoSuchRecordException; @Setter @Getter -public class ResultNext implements TestkitRequest +public class ResultNext extends AbstractResultNext { private ResultNextBody data; @Override - public TestkitResponse process( TestkitState testkitState ) - { - try - { - Result result = testkitState.getResultHolder( data.getResultId() ).getResult(); - return createResponse( result.next() ); - } - catch ( NoSuchRecordException ignored ) - { - return NullRecord.builder().build(); - } - } - - @Override - public CompletionStage processAsync( TestkitState testkitState ) - { - return testkitState.getAsyncResultHolder( data.getResultId() ) - .thenCompose( resultCursorHolder -> resultCursorHolder.getResult().nextAsync() ) - .thenApply( this::createResponseNullSafe ); - } - - @Override - public Mono processRx( TestkitState testkitState ) - { - return testkitState.getRxResultHolder( data.getResultId() ) - .flatMap( resultHolder -> - { - RxBufferedSubscriber subscriber = - resultHolder.getSubscriber() - .orElseGet( () -> - { - RxBufferedSubscriber subscriberInstance = - new RxBufferedSubscriber<>( - getFetchSize( resultHolder ) ); - resultHolder.setSubscriber( subscriberInstance ); - resultHolder.getResult().records() - .subscribe( subscriberInstance ); - return subscriberInstance; - } ); - return subscriber.next() - .map( this::createResponse ) - .defaultIfEmpty( NullRecord.builder().build() ); - } ); - } - - private long getFetchSize( RxResultHolder resultHolder ) - { - long fetchSize = resultHolder.getSessionHolder().getConfig() - .fetchSize() - .orElse( resultHolder.getSessionHolder().getDriverHolder().getConfig().fetchSize() ); - return fetchSize == -1 ? Long.MAX_VALUE : fetchSize; - } - - private neo4j.org.testkit.backend.messages.responses.TestkitResponse createResponse( Record record ) + protected neo4j.org.testkit.backend.messages.responses.TestkitResponse createResponse( Record record ) { return neo4j.org.testkit.backend.messages.responses.Record.builder() .data( neo4j.org.testkit.backend.messages.responses.Record.RecordBody.builder() @@ -102,9 +40,10 @@ private neo4j.org.testkit.backend.messages.responses.TestkitResponse createRespo .build(); } - private neo4j.org.testkit.backend.messages.responses.TestkitResponse createResponseNullSafe( Record record ) + @Override + protected String getResultId() { - return record != null ? createResponse( record ) : NullRecord.builder().build(); + return data.getResultId(); } @Setter diff --git a/testkit-backend/src/main/java/neo4j/org/testkit/backend/messages/requests/TestkitRequest.java b/testkit-backend/src/main/java/neo4j/org/testkit/backend/messages/requests/TestkitRequest.java index a2e09b606c..a404dd1426 100644 --- a/testkit-backend/src/main/java/neo4j/org/testkit/backend/messages/requests/TestkitRequest.java +++ b/testkit-backend/src/main/java/neo4j/org/testkit/backend/messages/requests/TestkitRequest.java @@ -41,7 +41,8 @@ @JsonSubTypes.Type( TransactionRollback.class ), @JsonSubTypes.Type( GetFeatures.class ), @JsonSubTypes.Type( GetRoutingTable.class ), @JsonSubTypes.Type( TransactionClose.class ), @JsonSubTypes.Type( ResultList.class ), @JsonSubTypes.Type( GetConnectionPoolMetrics.class ), - @JsonSubTypes.Type( ResultPeek.class ), @JsonSubTypes.Type( CheckDriverIsEncrypted.class ) + @JsonSubTypes.Type( ResultPeek.class ), @JsonSubTypes.Type( CheckDriverIsEncrypted.class ), + @JsonSubTypes.Type( CypherTypeField.class ) } ) public interface TestkitRequest { diff --git a/testkit-backend/src/main/java/neo4j/org/testkit/backend/messages/responses/Field.java b/testkit-backend/src/main/java/neo4j/org/testkit/backend/messages/responses/Field.java new file mode 100644 index 0000000000..f8690faef6 --- /dev/null +++ b/testkit-backend/src/main/java/neo4j/org/testkit/backend/messages/responses/Field.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 neo4j.org.testkit.backend.messages.responses; + +import lombok.Builder; +import lombok.Getter; + +@Getter +@Builder +public class Field implements TestkitResponse +{ + private final FieldBody data; + + @Override + public String testkitName() + { + return "Field"; + } + + @Getter + @Builder + public static class FieldBody + { + private final String value; + } +} 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 321662cab6..233c7a7923 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 @@ -24,6 +24,7 @@ import java.io.IOException; import java.util.function.Function; +import java.util.function.Supplier; import java.util.stream.StreamSupport; import org.neo4j.driver.internal.value.IntegerValue; @@ -49,7 +50,7 @@ public void serialize( NodeValue nodeValue, JsonGenerator gen, SerializerProvide cypherObject( gen, "Node", () -> { Node node = nodeValue.asNode(); - gen.writeObjectField( "id", new IntegerValue( node.id() ) ); + gen.writeObjectField( "id", new IntegerValue( getId( node::id ) ) ); gen.writeObjectField( "elementId", new StringValue( node.elementId() ) ); StringValue[] labels = StreamSupport.stream( node.labels().spliterator(), false ) @@ -60,4 +61,16 @@ public void serialize( NodeValue nodeValue, JsonGenerator gen, SerializerProvide gen.writeObjectField( "props", new MapValue( node.asMap( Function.identity() ) ) ); } ); } + + private long getId( Supplier supplier ) + { + try + { + return supplier.get(); + } + catch ( IllegalStateException e ) + { + return -1; + } + } } 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 1acfd3d6eb..2ded8f035f 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 @@ -24,6 +24,7 @@ import java.io.IOException; import java.util.function.Function; +import java.util.function.Supplier; import org.neo4j.driver.internal.value.IntegerValue; import org.neo4j.driver.internal.value.MapValue; @@ -46,14 +47,26 @@ public void serialize( RelationshipValue relationshipValue, JsonGenerator gen, S cypherObject( gen, "Relationship", () -> { Relationship relationship = relationshipValue.asRelationship(); - gen.writeObjectField( "id", new IntegerValue( relationship.id() ) ); + gen.writeObjectField( "id", new IntegerValue( getId( relationship::id ) ) ); gen.writeObjectField( "elementId", new StringValue( relationship.elementId() ) ); - gen.writeObjectField( "startNodeId", new IntegerValue( relationship.startNodeId() ) ); + gen.writeObjectField( "startNodeId", new IntegerValue( getId( relationship::startNodeId ) ) ); gen.writeObjectField( "startNodeElementId", new StringValue( relationship.startNodeElementId() ) ); - gen.writeObjectField( "endNodeId", new IntegerValue( relationship.endNodeId() ) ); + gen.writeObjectField( "endNodeId", new IntegerValue( getId( 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() ) ) ); } ); } + + private long getId( Supplier supplier ) + { + try + { + return supplier.get(); + } + catch ( IllegalStateException e ) + { + return -1; + } + } } From 31118f2b5e1d09fce4c5d9f9ae47b64119461701 Mon Sep 17 00:00:00 2001 From: Dmitriy Tverdiakov Date: Wed, 23 Mar 2022 10:50:14 +0000 Subject: [PATCH 2/2] Update error message to mention server --- .../main/java/org/neo4j/driver/internal/InternalEntity.java | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) 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 3f5d30ed3a..c45de858ec 100644 --- a/driver/src/main/java/org/neo4j/driver/internal/InternalEntity.java +++ b/driver/src/main/java/org/neo4j/driver/internal/InternalEntity.java @@ -32,7 +32,8 @@ public abstract class InternalEntity implements Entity, AsValue { - public static final String INVALID_ID_ERROR = "Numeric id is not available, please use string based element id alternative"; + public static final String INVALID_ID_ERROR = + "Numeric id is not available with this server deployment, please use the new string based element id alternative"; private final long id; private final String elementId;