Skip to content

Add exceptions to numeric id accessors in nodes and relationships #1192

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 2 commits into from
Mar 23, 2022
Merged
Show file tree
Hide file tree
Changes from 1 commit
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -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<String,Value> properties;
private final boolean numericIdAvailable;

public InternalEntity( long id, String elementId, Map<String,Value> properties )
public InternalEntity( long id, String elementId, Map<String,Value> properties, boolean numericIdAvailable )
{
this.id = id;
this.elementId = elementId;
this.properties = properties;
this.numericIdAvailable = numericIdAvailable;
}

@Override
public long id()
{
assertNumericIdAvailable();
return id;
}

Expand Down Expand Up @@ -142,4 +147,17 @@ public <T> Iterable<T> values( Function<Value,T> mapFunction )
{
return Iterables.map( properties.values(), mapFunction );
}

protected void assertNumericIdAvailable()
{
if ( !numericIdAvailable )
{
throw new IllegalStateException( INVALID_ID_ERROR );
}
}

public boolean isNumericIdAvailable()
{
return numericIdAvailable;
}
}
11 changes: 8 additions & 3 deletions driver/src/main/java/org/neo4j/driver/internal/InternalNode.java
Original file line number Diff line number Diff line change
Expand Up @@ -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<String> labels, Map<String,Value> properties )
public InternalNode( long id, Collection<String> labels, Map<String,Value> properties )
{
super( id, elementId, properties );
this( id, String.valueOf( id ), labels, properties, true );
}

public InternalNode( long id, String elementId, Collection<String> labels, Map<String,Value> properties, boolean numericIdAvailable )
{
super( id, elementId, properties, numericIdAvailable );
this.labels = labels;
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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<String,Value> 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<String,Value> properties )
Map<String,Value> properties, boolean numericIdAvailable )
{
super( id, elementId, properties );
super( id, elementId, properties, numericIdAvailable );
this.start = start;
this.startElementId = startElementId;
this.end = end;
Expand Down Expand Up @@ -72,6 +77,7 @@ public void setStartAndEnd( long start, String startElementId, long end, String
@Override
public long startNodeId()
{
assertNumericIdAvailable();
return start;
}

Expand All @@ -84,6 +90,7 @@ public String startNodeElementId()
@Override
public long endNodeId()
{
assertNumericIdAvailable();
return end;
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -237,8 +237,8 @@ protected Value unpackRelationship() throws IOException
String relType = unpacker.unpackString();
Map<String,Value> 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 );
}

Expand All @@ -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
Expand All @@ -283,7 +283,7 @@ protected Value unpackPath() throws IOException
long id = unpacker.unpackLong();
String relType = unpacker.unpackString();
Map<String,Value> 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
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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<String> labels = new ArrayList<>( numLabels );
Expand All @@ -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() );
Expand All @@ -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<String,Value> 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
Expand All @@ -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++ )
Expand All @@ -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;
Expand All @@ -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<String,Value> 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 );
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -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 )
Expand All @@ -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 ) );
}
Expand Down
5 changes: 3 additions & 2 deletions driver/src/main/java/org/neo4j/driver/types/Entity.java
Original file line number Diff line number Diff line change
Expand Up @@ -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.
* <p>
* 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
Expand Down
4 changes: 4 additions & 0 deletions driver/src/main/java/org/neo4j/driver/types/Relationship.java
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,8 @@ public interface Relationship extends Entity
{
/**
* Id of the node where this relationship starts.
* <p>
* 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()}
Expand All @@ -42,6 +44,8 @@ public interface Relationship extends Entity

/**
* Id of the node where this relationship ends.
* <p>
* 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()}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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 ) ) ) ) );
Expand Down
10 changes: 3 additions & 7 deletions driver/src/test/java/org/neo4j/driver/internal/ExtractTest.java
Original file line number Diff line number Diff line change
Expand Up @@ -127,7 +127,7 @@ void testProperties()
Map<String,Value> 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<Pair<String,Integer>> properties = Extract.properties( node, Value::asInt );
Expand All @@ -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<Pair<String,Integer>> fields = Extract.fields( record, Value::asInt );

Expand Down Expand Up @@ -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 ) ) ) ) );
}
}
Loading