diff --git a/driver/src/main/java/org/neo4j/driver/internal/InternalIsoDuration.java b/driver/src/main/java/org/neo4j/driver/internal/InternalIsoDuration.java new file mode 100644 index 0000000000..9afd8baef4 --- /dev/null +++ b/driver/src/main/java/org/neo4j/driver/internal/InternalIsoDuration.java @@ -0,0 +1,198 @@ +/* + * Copyright (c) 2002-2018 "Neo Technology," + * Network Engine for Objects in Lund AB [http://neotechnology.com] + * + * This file is part of Neo4j. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.neo4j.driver.internal; + +import java.time.Duration; +import java.time.Period; +import java.time.temporal.Temporal; +import java.time.temporal.TemporalUnit; +import java.time.temporal.UnsupportedTemporalTypeException; +import java.util.List; +import java.util.Objects; + +import org.neo4j.driver.v1.types.IsoDuration; + +import static java.time.temporal.ChronoUnit.DAYS; +import static java.time.temporal.ChronoUnit.MONTHS; +import static java.time.temporal.ChronoUnit.NANOS; +import static java.time.temporal.ChronoUnit.SECONDS; +import static java.util.Arrays.asList; +import static java.util.Collections.unmodifiableList; + +public class InternalIsoDuration implements IsoDuration +{ + private static final List SUPPORTED_UNITS = unmodifiableList( asList( MONTHS, DAYS, SECONDS, NANOS ) ); + + private final long months; + private final long days; + private final long seconds; + private final long nanoseconds; + + public InternalIsoDuration( Period period ) + { + this( period.toTotalMonths(), period.getDays(), 0, 0 ); + } + + public InternalIsoDuration( Duration duration ) + { + this( 0, 0, duration.getSeconds(), duration.getNano() ); + } + + public InternalIsoDuration( long months, long days, long seconds, long nanoseconds ) + { + this.months = months; + this.days = days; + this.seconds = seconds; + this.nanoseconds = nanoseconds; + } + + @Override + public long months() + { + return months; + } + + @Override + public long days() + { + return days; + } + + @Override + public long seconds() + { + return seconds; + } + + @Override + public long nanoseconds() + { + return nanoseconds; + } + + @Override + public long get( TemporalUnit unit ) + { + if ( unit == MONTHS ) + { + return months; + } + else if ( unit == DAYS ) + { + return days; + } + else if ( unit == SECONDS ) + { + return seconds; + } + else if ( unit == NANOS ) + { + return nanoseconds; + } + else + { + throw new UnsupportedTemporalTypeException( "Unsupported unit: " + unit ); + } + } + + @Override + public List getUnits() + { + return SUPPORTED_UNITS; + } + + @Override + public Temporal addTo( Temporal temporal ) + { + if ( months != 0 ) + { + temporal = temporal.plus( months, MONTHS ); + } + if ( days != 0 ) + { + temporal = temporal.plus( days, DAYS ); + } + if ( seconds != 0 ) + { + temporal = temporal.plus( seconds, SECONDS ); + } + if ( nanoseconds != 0 ) + { + temporal = temporal.plus( nanoseconds, NANOS ); + } + return temporal; + } + + @Override + public Temporal subtractFrom( Temporal temporal ) + { + if ( months != 0 ) + { + temporal = temporal.minus( months, MONTHS ); + } + if ( days != 0 ) + { + temporal = temporal.minus( days, DAYS ); + } + if ( seconds != 0 ) + { + temporal = temporal.minus( seconds, SECONDS ); + } + if ( nanoseconds != 0 ) + { + temporal = temporal.minus( nanoseconds, NANOS ); + } + return temporal; + } + + @Override + public boolean equals( Object o ) + { + if ( this == o ) + { + return true; + } + if ( o == null || getClass() != o.getClass() ) + { + return false; + } + InternalIsoDuration that = (InternalIsoDuration) o; + return months == that.months && + days == that.days && + seconds == that.seconds && + nanoseconds == that.nanoseconds; + } + + @Override + public int hashCode() + { + return Objects.hash( months, days, seconds, nanoseconds ); + } + + @Override + public String toString() + { + return "Duration{" + + "months=" + months + + ", days=" + days + + ", seconds=" + seconds + + ", nanoseconds=" + nanoseconds + + '}'; + } +} diff --git a/driver/src/main/java/org/neo4j/driver/internal/InternalRecord.java b/driver/src/main/java/org/neo4j/driver/internal/InternalRecord.java index 01966de341..101ed38966 100644 --- a/driver/src/main/java/org/neo4j/driver/internal/InternalRecord.java +++ b/driver/src/main/java/org/neo4j/driver/internal/InternalRecord.java @@ -23,9 +23,8 @@ import java.util.Map; import java.util.NoSuchElementException; -import org.neo4j.driver.internal.util.Extract; -import org.neo4j.driver.internal.value.InternalValue; import org.neo4j.driver.internal.types.InternalMapAccessorWithDefaultValue; +import org.neo4j.driver.internal.util.Extract; import org.neo4j.driver.v1.Record; import org.neo4j.driver.v1.Value; import org.neo4j.driver.v1.Values; @@ -129,7 +128,7 @@ public Map asMap( Function mapper ) @Override public String toString() { - return format( "Record<%s>", formatPairs( InternalValue.Format.VALUE_ONLY, asMap( ofValue() ) ) ); + return format( "Record<%s>", formatPairs( asMap( ofValue() ) ) ); } @Override diff --git a/driver/src/main/java/org/neo4j/driver/internal/messaging/PackStreamMessageFormatV1.java b/driver/src/main/java/org/neo4j/driver/internal/messaging/PackStreamMessageFormatV1.java index 837b011839..c5124ad257 100644 --- a/driver/src/main/java/org/neo4j/driver/internal/messaging/PackStreamMessageFormatV1.java +++ b/driver/src/main/java/org/neo4j/driver/internal/messaging/PackStreamMessageFormatV1.java @@ -33,6 +33,7 @@ import org.neo4j.driver.internal.packstream.PackOutput; import org.neo4j.driver.internal.packstream.PackStream; import org.neo4j.driver.internal.packstream.PackType; +import org.neo4j.driver.internal.types.TypeConstructor; import org.neo4j.driver.internal.util.Iterables; import org.neo4j.driver.internal.value.InternalValue; import org.neo4j.driver.internal.value.ListValue; @@ -210,120 +211,120 @@ void packInternalValue( InternalValue value ) throws IOException { switch ( value.typeConstructor() ) { - case NULL_TyCon: - packer.packNull(); - break; + case NULL: + packer.packNull(); + break; - case BYTES_TyCon: - packer.pack( value.asByteArray() ); - break; + case BYTES: + packer.pack( value.asByteArray() ); + break; - case STRING_TyCon: - packer.pack( value.asString() ); - break; + case STRING: + packer.pack( value.asString() ); + break; - case BOOLEAN_TyCon: - packer.pack( value.asBoolean() ); - break; + case BOOLEAN: + packer.pack( value.asBoolean() ); + break; - case INTEGER_TyCon: - packer.pack( value.asLong() ); - break; + case INTEGER: + packer.pack( value.asLong() ); + break; - case FLOAT_TyCon: - packer.pack( value.asDouble() ); - break; + case FLOAT: + packer.pack( value.asDouble() ); + break; - case MAP_TyCon: - packer.packMapHeader( value.size() ); - for ( String s : value.keys() ) - { - packer.pack( s ); - packValue( value.get( s ) ); - } - break; + case MAP: + packer.packMapHeader( value.size() ); + for ( String s : value.keys() ) + { + packer.pack( s ); + packValue( value.get( s ) ); + } + break; - case LIST_TyCon: - packer.packListHeader( value.size() ); - for ( Value item : value.values() ) - { - packValue( item ); - } - break; + case LIST: + packer.packListHeader( value.size() ); + for ( Value item : value.values() ) + { + packValue( item ); + } + break; - case NODE_TyCon: - { - Node node = value.asNode(); - packNode( node ); - } - break; + case NODE: + { + Node node = value.asNode(); + packNode( node ); + } + break; - case RELATIONSHIP_TyCon: - { - Relationship rel = value.asRelationship(); - packer.packStructHeader( 5, RELATIONSHIP ); - packer.pack( rel.id() ); - packer.pack( rel.startNodeId() ); - packer.pack( rel.endNodeId() ); + case RELATIONSHIP: + { + Relationship rel = value.asRelationship(); + packer.packStructHeader( 5, RELATIONSHIP ); + packer.pack( rel.id() ); + packer.pack( rel.startNodeId() ); + packer.pack( rel.endNodeId() ); - packer.pack( rel.type() ); + packer.pack( rel.type() ); - packProperties( rel ); - } - break; + packProperties( rel ); + } + break; - case PATH_TyCon: - Path path = value.asPath(); - packer.packStructHeader( 3, PATH ); + case PATH: + Path path = value.asPath(); + packer.packStructHeader( 3, PATH ); - // Unique nodes - Map nodeIdx = Iterables.newLinkedHashMapWithSize( path.length() + 1 ); - for ( Node node : path.nodes() ) - { - if ( !nodeIdx.containsKey( node ) ) - { - nodeIdx.put( node, nodeIdx.size() ); - } - } - packer.packListHeader( nodeIdx.size() ); - for ( Node node : nodeIdx.keySet() ) + // Unique nodes + Map nodeIdx = Iterables.newLinkedHashMapWithSize( path.length() + 1 ); + for ( Node node : path.nodes() ) + { + if ( !nodeIdx.containsKey( node ) ) { - packNode( node ); + nodeIdx.put( node, nodeIdx.size() ); } + } + packer.packListHeader( nodeIdx.size() ); + for ( Node node : nodeIdx.keySet() ) + { + packNode( node ); + } - // Unique rels - Map relIdx = Iterables.newLinkedHashMapWithSize( path.length() ); - for ( Relationship rel : path.relationships() ) - { - if ( !relIdx.containsKey( rel ) ) - { - relIdx.put( rel, relIdx.size() + 1 ); - } - } - packer.packListHeader( relIdx.size() ); - for ( Relationship rel : relIdx.keySet() ) + // Unique rels + Map relIdx = Iterables.newLinkedHashMapWithSize( path.length() ); + for ( Relationship rel : path.relationships() ) + { + if ( !relIdx.containsKey( rel ) ) { - packer.packStructHeader( 3, UNBOUND_RELATIONSHIP ); - packer.pack( rel.id() ); - packer.pack( rel.type() ); - packProperties( rel ); + relIdx.put( rel, relIdx.size() + 1 ); } + } + packer.packListHeader( relIdx.size() ); + for ( Relationship rel : relIdx.keySet() ) + { + packer.packStructHeader( 3, UNBOUND_RELATIONSHIP ); + packer.pack( rel.id() ); + packer.pack( rel.type() ); + packProperties( rel ); + } - // Sequence - packer.packListHeader( path.length() * 2 ); - for ( Path.Segment seg : path ) - { - Relationship rel = seg.relationship(); - long relEndId = rel.endNodeId(); - long segEndId = seg.end().id(); - int size = relEndId == segEndId ? relIdx.get( rel ) : -relIdx.get( rel ); - packer.pack( size ); - packer.pack( nodeIdx.get( seg.end() ) ); - } - break; + // Sequence + packer.packListHeader( path.length() * 2 ); + for ( Path.Segment seg : path ) + { + Relationship rel = seg.relationship(); + long relEndId = rel.endNodeId(); + long segEndId = seg.end().id(); + int size = relEndId == segEndId ? relIdx.get( rel ) : -relIdx.get( rel ); + packer.pack( size ); + packer.pack( nodeIdx.get( seg.end() ) ); + } + break; - default: - throw new IOException( "Unknown type: " + value ); + default: + throw new IOException( "Unknown type: " + value ); } } @@ -514,14 +515,14 @@ Value unpackStruct( long size, byte type ) throws IOException switch ( type ) { case NODE: - ensureCorrectStructSize( "NODE", NODE_FIELDS, size ); + ensureCorrectStructSize( TypeConstructor.NODE, NODE_FIELDS, size ); InternalNode adapted = unpackNode(); return new NodeValue( adapted ); case RELATIONSHIP: - ensureCorrectStructSize( "RELATIONSHIP", 5, size ); + ensureCorrectStructSize( TypeConstructor.RELATIONSHIP, 5, size ); return unpackRelationship(); case PATH: - ensureCorrectStructSize( "PATH", 3, size ); + ensureCorrectStructSize( TypeConstructor.PATH, 3, size ); return unpackPath(); default: throw new IOException( "Unknown struct type: " + type ); @@ -567,7 +568,7 @@ private Value unpackPath() throws IOException Node[] uniqNodes = new Node[(int) unpacker.unpackListHeader()]; for ( int i = 0; i < uniqNodes.length; i++ ) { - ensureCorrectStructSize( "NODE", NODE_FIELDS, unpacker.unpackStructHeader() ); + ensureCorrectStructSize( TypeConstructor.NODE, NODE_FIELDS, unpacker.unpackStructHeader() ); ensureCorrectStructSignature( "NODE", NODE, unpacker.unpackStructSignature() ); uniqNodes[i] = unpackNode(); } @@ -576,7 +577,7 @@ private Value unpackPath() throws IOException InternalRelationship[] uniqRels = new InternalRelationship[(int) unpacker.unpackListHeader()]; for ( int i = 0; i < uniqRels.length; i++ ) { - ensureCorrectStructSize( "RELATIONSHIP", 3, unpacker.unpackStructHeader() ); + ensureCorrectStructSize( TypeConstructor.RELATIONSHIP, 3, unpacker.unpackStructHeader() ); ensureCorrectStructSignature( "UNBOUND_RELATIONSHIP", UNBOUND_RELATIONSHIP, unpacker.unpackStructSignature() ); long id = unpacker.unpackLong(); String relType = unpacker.unpackString(); @@ -619,10 +620,11 @@ private Value unpackPath() throws IOException return new PathValue( new InternalPath( Arrays.asList( segments ), Arrays.asList( nodes ), Arrays.asList( rels ) ) ); } - void ensureCorrectStructSize( String structName, int expected, long actual ) + void ensureCorrectStructSize( TypeConstructor typeConstructor, int expected, long actual ) { if ( expected != actual ) { + String structName = typeConstructor.toString(); throw new ClientException( String.format( "Invalid message received, serialized %s structures should have %d fields, " + "received %s structure has %d fields.", structName, expected, structName, actual ) ); diff --git a/driver/src/main/java/org/neo4j/driver/internal/messaging/PackStreamMessageFormatV2.java b/driver/src/main/java/org/neo4j/driver/internal/messaging/PackStreamMessageFormatV2.java index 0cd8f90ab4..8146a1e5a2 100644 --- a/driver/src/main/java/org/neo4j/driver/internal/messaging/PackStreamMessageFormatV2.java +++ b/driver/src/main/java/org/neo4j/driver/internal/messaging/PackStreamMessageFormatV2.java @@ -19,27 +19,55 @@ package org.neo4j.driver.internal.messaging; import java.io.IOException; +import java.time.Instant; +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 org.neo4j.driver.internal.InternalPoint2D; -import org.neo4j.driver.internal.InternalPoint3D; import org.neo4j.driver.internal.packstream.PackInput; import org.neo4j.driver.internal.packstream.PackOutput; +import org.neo4j.driver.internal.types.TypeConstructor; import org.neo4j.driver.internal.value.InternalValue; -import org.neo4j.driver.internal.value.Point2DValue; -import org.neo4j.driver.internal.value.Point3DValue; import org.neo4j.driver.v1.Value; +import org.neo4j.driver.v1.types.IsoDuration; import org.neo4j.driver.v1.types.Point2D; import org.neo4j.driver.v1.types.Point3D; -import static org.neo4j.driver.internal.types.TypeConstructor.POINT_2D_TyCon; -import static org.neo4j.driver.internal.types.TypeConstructor.POINT_3D_TyCon; +import static java.time.ZoneOffset.UTC; +import static org.neo4j.driver.v1.Values.isoDuration; +import static org.neo4j.driver.v1.Values.point2D; +import static org.neo4j.driver.v1.Values.point3D; +import static org.neo4j.driver.v1.Values.value; public class PackStreamMessageFormatV2 extends PackStreamMessageFormatV1 { - private static final byte POINT_2D_STRUCT_TYPE = 'X'; - private static final byte POINT_3D_STRUCT_TYPE = 'Y'; + private static final byte DATE = 'D'; + private static final int DATE_STRUCT_SIZE = 1; + + private static final byte TIME = 'T'; + private static final int TIME_STRUCT_SIZE = 2; + + private static final byte LOCAL_TIME = 't'; + private static final int LOCAL_TIME_STRUCT_SIZE = 1; + + private static final byte LOCAL_DATE_TIME = 'd'; + private static final int LOCAL_DATE_TIME_STRUCT_SIZE = 2; + private static final byte DATE_TIME_WITH_ZONE_OFFSET = 'F'; + private static final byte DATE_TIME_WITH_ZONE_ID = 'f'; + private static final int DATE_TIME_STRUCT_SIZE = 3; + + private static final byte DURATION = 'E'; + private static final int DURATION_TIME_STRUCT_SIZE = 4; + + private static final byte POINT_2D_STRUCT_TYPE = 'X'; private static final int POINT_2D_STRUCT_SIZE = 3; + + private static final byte POINT_3D_STRUCT_TYPE = 'Y'; private static final int POINT_3D_STRUCT_SIZE = 4; @Override @@ -68,20 +96,105 @@ private static class WriterV2 extends WriterV1 @Override void packInternalValue( InternalValue value ) throws IOException { - if ( value.typeConstructor() == POINT_2D_TyCon ) + TypeConstructor typeConstructor = value.typeConstructor(); + switch ( typeConstructor ) { + case DATE: + packDate( value.asLocalDate() ); + break; + case TIME: + packTime( value.asOffsetTime() ); + break; + case LOCAL_TIME: + packLocalTime( value.asLocalTime() ); + break; + case LOCAL_DATE_TIME: + packLocalDateTime( value.asLocalDateTime() ); + break; + case DATE_TIME: + packZonedDateTime( value.asZonedDateTime() ); + break; + case DURATION: + packDuration( value.asIsoDuration() ); + break; + case POINT_2D: packPoint2D( value.asPoint2D() ); + break; + case POINT_3D: + packPoint3D( value.asPoint3D() ); + break; + default: + super.packInternalValue( value ); } - else if ( value.typeConstructor() == POINT_3D_TyCon ) + } + + private void packDate( LocalDate localDate ) throws IOException + { + packer.packStructHeader( DATE_STRUCT_SIZE, DATE ); + packer.pack( localDate.toEpochDay() ); + } + + private void packTime( OffsetTime offsetTime ) throws IOException + { + OffsetTime offsetTimeUtc = offsetTime.withOffsetSameInstant( UTC ); + long nanoOfDayUtc = offsetTimeUtc.toLocalTime().toNanoOfDay(); + int offsetSeconds = offsetTime.getOffset().getTotalSeconds(); + + packer.packStructHeader( TIME_STRUCT_SIZE, TIME ); + packer.pack( nanoOfDayUtc ); + packer.pack( offsetSeconds ); + } + + private void packLocalTime( LocalTime localTime ) throws IOException + { + packer.packStructHeader( LOCAL_TIME_STRUCT_SIZE, LOCAL_TIME ); + packer.pack( localTime.toNanoOfDay() ); + } + + private void packLocalDateTime( LocalDateTime localDateTime ) throws IOException + { + long epochSecondUtc = localDateTime.toEpochSecond( UTC ); + int nano = localDateTime.getNano(); + + packer.packStructHeader( LOCAL_DATE_TIME_STRUCT_SIZE, LOCAL_DATE_TIME ); + packer.pack( epochSecondUtc ); + packer.pack( nano ); + } + + private void packZonedDateTime( ZonedDateTime zonedDateTime ) throws IOException + { + Instant instant = zonedDateTime.toInstant(); + ZoneId zone = zonedDateTime.getZone(); + + if ( zone instanceof ZoneOffset ) { - packPoint3D( value.asPoint3D() ); + int offsetSeconds = ((ZoneOffset) zone).getTotalSeconds(); + + packer.packStructHeader( DATE_TIME_STRUCT_SIZE, DATE_TIME_WITH_ZONE_OFFSET ); + packer.pack( instant.getEpochSecond() ); + packer.pack( instant.getNano() ); + packer.pack( offsetSeconds ); } else { - super.packInternalValue( value ); + String zoneId = zone.getId(); + + packer.packStructHeader( DATE_TIME_STRUCT_SIZE, DATE_TIME_WITH_ZONE_ID ); + packer.pack( instant.getEpochSecond() ); + packer.pack( instant.getNano() ); + packer.pack( zoneId ); } } + private void packDuration( IsoDuration duration ) throws IOException + { + packer.packStructHeader( DURATION_TIME_STRUCT_SIZE, DURATION ); + packer.pack( duration.months() ); + packer.pack( duration.days() ); + packer.pack( duration.seconds() ); + packer.pack( duration.nanoseconds() ); + } + private void packPoint2D( Point2D point ) throws IOException { packer.packStructHeader( POINT_2D_STRUCT_SIZE, POINT_2D_STRUCT_TYPE ); @@ -110,28 +223,106 @@ private static class ReaderV2 extends ReaderV1 @Override Value unpackStruct( long size, byte type ) throws IOException { - if ( type == POINT_2D_STRUCT_TYPE ) + switch ( type ) { - ensureCorrectStructSize( POINT_2D_TyCon.typeName(), POINT_2D_STRUCT_SIZE, size ); + case DATE: + ensureCorrectStructSize( TypeConstructor.DATE, DATE_STRUCT_SIZE, size ); + return unpackDate(); + case TIME: + ensureCorrectStructSize( TypeConstructor.TIME, TIME_STRUCT_SIZE, size ); + return unpackTime(); + case LOCAL_TIME: + ensureCorrectStructSize( TypeConstructor.LOCAL_TIME, LOCAL_TIME_STRUCT_SIZE, size ); + return unpackLocalTime(); + case LOCAL_DATE_TIME: + ensureCorrectStructSize( TypeConstructor.LOCAL_DATE_TIME, LOCAL_DATE_TIME_STRUCT_SIZE, size ); + return unpackLocalDateTime(); + case DATE_TIME_WITH_ZONE_OFFSET: + ensureCorrectStructSize( TypeConstructor.DATE_TIME, DATE_TIME_STRUCT_SIZE, size ); + return unpackDateTimeWithZoneOffset(); + case DATE_TIME_WITH_ZONE_ID: + ensureCorrectStructSize( TypeConstructor.DATE_TIME, DATE_TIME_STRUCT_SIZE, size ); + return unpackDateTimeWithZoneId(); + case DURATION: + ensureCorrectStructSize( TypeConstructor.DURATION, DURATION_TIME_STRUCT_SIZE, size ); + return unpackDuration(); + case POINT_2D_STRUCT_TYPE: + ensureCorrectStructSize( TypeConstructor.POINT_2D, POINT_2D_STRUCT_SIZE, size ); return unpackPoint2D(); - } - else if ( type == POINT_3D_STRUCT_TYPE ) - { - ensureCorrectStructSize( POINT_3D_TyCon.typeName(), POINT_3D_STRUCT_SIZE, size ); + case POINT_3D_STRUCT_TYPE: + ensureCorrectStructSize( TypeConstructor.POINT_3D, POINT_3D_STRUCT_SIZE, size ); return unpackPoint3D(); - } - else - { + default: return super.unpackStruct( size, type ); } } + private Value unpackDate() throws IOException + { + long epochDay = unpacker.unpackLong(); + return value( LocalDate.ofEpochDay( epochDay ) ); + } + + private Value unpackTime() throws IOException + { + long nanoOfDayUtc = unpacker.unpackLong(); + int offsetSeconds = Math.toIntExact( unpacker.unpackLong() ); + + Instant instant = Instant.ofEpochSecond( 0, nanoOfDayUtc ); + ZoneOffset offset = ZoneOffset.ofTotalSeconds( offsetSeconds ); + return value( OffsetTime.ofInstant( instant, offset ) ); + } + + private Value unpackLocalTime() throws IOException + { + long nanoOfDay = unpacker.unpackLong(); + return value( LocalTime.ofNanoOfDay( nanoOfDay ) ); + } + + private Value unpackLocalDateTime() throws IOException + { + long epochSecondUtc = unpacker.unpackLong(); + int nano = Math.toIntExact( unpacker.unpackLong() ); + return value( LocalDateTime.ofEpochSecond( epochSecondUtc, nano, UTC ) ); + } + + private Value unpackDateTimeWithZoneOffset() throws IOException + { + long epochSecondUtc = unpacker.unpackLong(); + int nano = Math.toIntExact( unpacker.unpackLong() ); + int offsetSeconds = Math.toIntExact( unpacker.unpackLong() ); + + Instant instant = Instant.ofEpochSecond( epochSecondUtc, nano ); + ZoneOffset zoneOffset = ZoneOffset.ofTotalSeconds( offsetSeconds ); + return value( ZonedDateTime.ofInstant( instant, zoneOffset ) ); + } + + private Value unpackDateTimeWithZoneId() throws IOException + { + long epochSecondUtc = unpacker.unpackLong(); + int nano = Math.toIntExact( unpacker.unpackLong() ); + String zoneIdString = unpacker.unpackString(); + + Instant instant = Instant.ofEpochSecond( epochSecondUtc, nano ); + ZoneId zoneId = ZoneId.of( zoneIdString ); + return value( ZonedDateTime.ofInstant( instant, zoneId ) ); + } + + private Value unpackDuration() throws IOException + { + long months = unpacker.unpackLong(); + long days = unpacker.unpackLong(); + long seconds = unpacker.unpackLong(); + long nanoseconds = unpacker.unpackLong(); + return isoDuration( months, days, seconds, nanoseconds ); + } + private Value unpackPoint2D() throws IOException { long srid = unpacker.unpackLong(); double x = unpacker.unpackDouble(); double y = unpacker.unpackDouble(); - return new Point2DValue( new InternalPoint2D( srid, x, y ) ); + return point2D( srid, x, y ); } private Value unpackPoint3D() throws IOException @@ -140,7 +331,7 @@ private Value unpackPoint3D() throws IOException double x = unpacker.unpackDouble(); double y = unpacker.unpackDouble(); double z = unpacker.unpackDouble(); - return new Point3DValue( new InternalPoint3D( srid, x, y, z ) ); + return point3D( srid, x, y, z ); } } } diff --git a/driver/src/main/java/org/neo4j/driver/internal/types/InternalTypeSystem.java b/driver/src/main/java/org/neo4j/driver/internal/types/InternalTypeSystem.java index d184dee9aa..645a01f5a4 100644 --- a/driver/src/main/java/org/neo4j/driver/internal/types/InternalTypeSystem.java +++ b/driver/src/main/java/org/neo4j/driver/internal/types/InternalTypeSystem.java @@ -22,21 +22,27 @@ import org.neo4j.driver.v1.types.Type; import org.neo4j.driver.v1.types.TypeSystem; -import static org.neo4j.driver.internal.types.TypeConstructor.ANY_TyCon; -import static org.neo4j.driver.internal.types.TypeConstructor.BOOLEAN_TyCon; -import static org.neo4j.driver.internal.types.TypeConstructor.BYTES_TyCon; -import static org.neo4j.driver.internal.types.TypeConstructor.FLOAT_TyCon; -import static org.neo4j.driver.internal.types.TypeConstructor.INTEGER_TyCon; -import static org.neo4j.driver.internal.types.TypeConstructor.LIST_TyCon; -import static org.neo4j.driver.internal.types.TypeConstructor.MAP_TyCon; -import static org.neo4j.driver.internal.types.TypeConstructor.NODE_TyCon; -import static org.neo4j.driver.internal.types.TypeConstructor.NULL_TyCon; -import static org.neo4j.driver.internal.types.TypeConstructor.NUMBER_TyCon; -import static org.neo4j.driver.internal.types.TypeConstructor.PATH_TyCon; -import static org.neo4j.driver.internal.types.TypeConstructor.POINT_2D_TyCon; -import static org.neo4j.driver.internal.types.TypeConstructor.POINT_3D_TyCon; -import static org.neo4j.driver.internal.types.TypeConstructor.RELATIONSHIP_TyCon; -import static org.neo4j.driver.internal.types.TypeConstructor.STRING_TyCon; +import static org.neo4j.driver.internal.types.TypeConstructor.ANY; +import static org.neo4j.driver.internal.types.TypeConstructor.BOOLEAN; +import static org.neo4j.driver.internal.types.TypeConstructor.BYTES; +import static org.neo4j.driver.internal.types.TypeConstructor.DATE; +import static org.neo4j.driver.internal.types.TypeConstructor.DATE_TIME; +import static org.neo4j.driver.internal.types.TypeConstructor.DURATION; +import static org.neo4j.driver.internal.types.TypeConstructor.FLOAT; +import static org.neo4j.driver.internal.types.TypeConstructor.INTEGER; +import static org.neo4j.driver.internal.types.TypeConstructor.LIST; +import static org.neo4j.driver.internal.types.TypeConstructor.LOCAL_DATE_TIME; +import static org.neo4j.driver.internal.types.TypeConstructor.LOCAL_TIME; +import static org.neo4j.driver.internal.types.TypeConstructor.MAP; +import static org.neo4j.driver.internal.types.TypeConstructor.NODE; +import static org.neo4j.driver.internal.types.TypeConstructor.NULL; +import static org.neo4j.driver.internal.types.TypeConstructor.NUMBER; +import static org.neo4j.driver.internal.types.TypeConstructor.PATH; +import static org.neo4j.driver.internal.types.TypeConstructor.POINT_2D; +import static org.neo4j.driver.internal.types.TypeConstructor.POINT_3D; +import static org.neo4j.driver.internal.types.TypeConstructor.RELATIONSHIP; +import static org.neo4j.driver.internal.types.TypeConstructor.STRING; +import static org.neo4j.driver.internal.types.TypeConstructor.TIME; /** * Utility class for determining and working with the Cypher types of values @@ -48,104 +54,98 @@ public class InternalTypeSystem implements TypeSystem { public static InternalTypeSystem TYPE_SYSTEM = new InternalTypeSystem(); - private final TypeRepresentation anyType = constructType( ANY_TyCon ); - private final TypeRepresentation booleanType = constructType( BOOLEAN_TyCon ); - private final TypeRepresentation bytesType = constructType( BYTES_TyCon ); - private final TypeRepresentation stringType = constructType( STRING_TyCon ); - private final TypeRepresentation numberType = constructType( NUMBER_TyCon ); - private final TypeRepresentation integerType = constructType( INTEGER_TyCon ); - private final TypeRepresentation floatType = constructType( FLOAT_TyCon ); - private final TypeRepresentation listType = constructType( LIST_TyCon ); - private final TypeRepresentation mapType = constructType( MAP_TyCon ); - private final TypeRepresentation nodeType = constructType( NODE_TyCon ); - private final TypeRepresentation relationshipType = constructType( RELATIONSHIP_TyCon ); - private final TypeRepresentation pathType = constructType( PATH_TyCon ); - private final TypeRepresentation point2dType = constructType( POINT_2D_TyCon ); - private final TypeRepresentation point3dType = constructType( POINT_3D_TyCon ); - private final TypeRepresentation nullType = constructType( NULL_TyCon ); + private final TypeRepresentation anyType = constructType( ANY ); + private final TypeRepresentation booleanType = constructType( BOOLEAN ); + private final TypeRepresentation bytesType = constructType( BYTES ); + private final TypeRepresentation stringType = constructType( STRING ); + private final TypeRepresentation numberType = constructType( NUMBER ); + private final TypeRepresentation integerType = constructType( INTEGER ); + private final TypeRepresentation floatType = constructType( FLOAT ); + private final TypeRepresentation listType = constructType( LIST ); + private final TypeRepresentation mapType = constructType( MAP ); + private final TypeRepresentation nodeType = constructType( NODE ); + private final TypeRepresentation relationshipType = constructType( RELATIONSHIP ); + private final TypeRepresentation pathType = constructType( PATH ); + private final TypeRepresentation point2dType = constructType( POINT_2D ); + private final TypeRepresentation point3dType = constructType( POINT_3D ); + private final TypeRepresentation dateType = constructType( DATE ); + private final TypeRepresentation timeType = constructType( TIME ); + private final TypeRepresentation localTimeType = constructType( LOCAL_TIME ); + private final TypeRepresentation localDateTimeType = constructType( LOCAL_DATE_TIME ); + private final TypeRepresentation dateTimeType = constructType( DATE_TIME ); + private final TypeRepresentation durationType = constructType( DURATION ); + private final TypeRepresentation nullType = constructType( NULL ); private InternalTypeSystem() { } - /** the Cypher type ANY */ @Override public Type ANY() { return anyType; } - /** the Cypher type BOOLEAN */ @Override public Type BOOLEAN() { return booleanType; } - /** the Cypher type BYTES */ @Override public Type BYTES() { return bytesType; } - /** the Cypher type STRING */ @Override public Type STRING() { return stringType; } - /** the Cypher type NUMBER */ @Override public Type NUMBER() { return numberType; } - /** the Cypher type INTEGER */ @Override public Type INTEGER() { return integerType; } - /** the Cypher type FLOAT */ @Override public Type FLOAT() { return floatType; } - /** the Cypher type LIST */ @Override public Type LIST() { return listType; } - /** the Cypher type MAP */ @Override public Type MAP() { return mapType; } - /** the Cypher type NODE */ @Override public Type NODE() { return nodeType; } - /** the Cypher type RELATIONSHIP */ @Override public Type RELATIONSHIP() { return relationshipType; } - /** the Cypher type PATH */ @Override public Type PATH() { @@ -164,7 +164,42 @@ public Type POINT_3D() return point3dType; } - /** the Cypher type NULL */ + @Override + public Type DATE() + { + return dateType; + } + + @Override + public Type TIME() + { + return timeType; + } + + @Override + public Type LOCAL_TIME() + { + return localTimeType; + } + + @Override + public Type LOCAL_DATE_TIME() + { + return localDateTimeType; + } + + @Override + public Type DATE_TIME() + { + return dateTimeType; + } + + @Override + public Type DURATION() + { + return durationType; + } + @Override public Type NULL() { diff --git a/driver/src/main/java/org/neo4j/driver/internal/types/TypeConstructor.java b/driver/src/main/java/org/neo4j/driver/internal/types/TypeConstructor.java index 0a037f40d1..61f41d22ff 100644 --- a/driver/src/main/java/org/neo4j/driver/internal/types/TypeConstructor.java +++ b/driver/src/main/java/org/neo4j/driver/internal/types/TypeConstructor.java @@ -23,155 +23,56 @@ public enum TypeConstructor { - ANY_TyCon { - @Override - public String typeName() - { - return "ANY"; - } - - @Override - public boolean covers( Value value ) - { - return ! value.isNull(); - } - }, - BOOLEAN_TyCon { - @Override - public String typeName() - { - return "BOOLEAN"; - } - }, - - BYTES_TyCon { - @Override - public String typeName() - { - return "BYTES"; - } - }, - - STRING_TyCon { - @Override - public String typeName() - { - return "STRING"; - } - }, - - NUMBER_TyCon { - @Override - public boolean covers( Value value ) - { - TypeConstructor valueType = typeConstructorOf( value ); - return valueType == this || valueType == INTEGER_TyCon || valueType == FLOAT_TyCon; - } - - @Override - public String typeName() - { - return "NUMBER"; - } - }, - - INTEGER_TyCon { - @Override - public String typeName() - { - return "INTEGER"; - } - }, - - FLOAT_TyCon { - @Override - public String typeName() - { - return "FLOAT"; - } - }, - - LIST_TyCon { - @Override - public String typeName() - { - return "LIST"; - } - }, - - MAP_TyCon { - @Override - public String typeName() - { - return "MAP"; - } - - @Override - public boolean covers( Value value ) - { - TypeConstructor valueType = typeConstructorOf( value ); - return valueType == MAP_TyCon || valueType == NODE_TyCon || valueType == RELATIONSHIP_TyCon; - } - }, - - NODE_TyCon { - @Override - public String typeName() - { - return "NODE"; - } - }, - - RELATIONSHIP_TyCon + ANY { - @Override - public String typeName() - { - return "RELATIONSHIP"; - } - }, - - PATH_TyCon { - @Override - public String typeName() - { - return "PATH"; - } - }, - - POINT_2D_TyCon + @Override + public boolean covers( Value value ) + { + return !value.isNull(); + } + }, + BOOLEAN, + BYTES, + STRING, + NUMBER { @Override - public String typeName() + public boolean covers( Value value ) { - return "POINT"; + TypeConstructor valueType = typeConstructorOf( value ); + return valueType == this || valueType == INTEGER || valueType == FLOAT; } }, - - POINT_3D_TyCon + INTEGER, + FLOAT, + LIST, + MAP { @Override - public String typeName() + public boolean covers( Value value ) { - return "POINT"; + TypeConstructor valueType = typeConstructorOf( value ); + return valueType == MAP || valueType == NODE || valueType == RELATIONSHIP; } }, - - NULL_TyCon { - @Override - public String typeName() - { - return "NULL"; - } - }; + NODE, + RELATIONSHIP, + PATH, + POINT_2D, + POINT_3D, + DATE, + TIME, + LOCAL_TIME, + LOCAL_DATE_TIME, + DATE_TIME, + DURATION, + NULL; private static TypeConstructor typeConstructorOf( Value value ) { - return ( (InternalValue) value ).typeConstructor(); + return ((InternalValue) value).typeConstructor(); } - public abstract String typeName(); - public boolean covers( Value value ) { return this == typeConstructorOf( value ); diff --git a/driver/src/main/java/org/neo4j/driver/internal/types/TypeRepresentation.java b/driver/src/main/java/org/neo4j/driver/internal/types/TypeRepresentation.java index 69338c98e5..522640eb50 100644 --- a/driver/src/main/java/org/neo4j/driver/internal/types/TypeRepresentation.java +++ b/driver/src/main/java/org/neo4j/driver/internal/types/TypeRepresentation.java @@ -18,10 +18,10 @@ */ package org.neo4j.driver.internal.types; -import org.neo4j.driver.v1.types.Type; import org.neo4j.driver.v1.Value; +import org.neo4j.driver.v1.types.Type; -import static org.neo4j.driver.internal.types.TypeConstructor.LIST_TyCon; +import static org.neo4j.driver.internal.types.TypeConstructor.LIST; public class TypeRepresentation implements Type { @@ -41,12 +41,12 @@ public boolean isTypeOf( Value value ) @Override public String name() { - if (tyCon == LIST_TyCon) + if ( tyCon == LIST ) { return "LIST OF ANY?"; } - return tyCon.typeName(); + return tyCon.toString(); } public TypeConstructor constructor() diff --git a/driver/src/main/java/org/neo4j/driver/internal/util/Format.java b/driver/src/main/java/org/neo4j/driver/internal/util/Format.java index ca8592eddf..cd14991982 100644 --- a/driver/src/main/java/org/neo4j/driver/internal/util/Format.java +++ b/driver/src/main/java/org/neo4j/driver/internal/util/Format.java @@ -22,8 +22,6 @@ import java.util.Map; import java.util.Map.Entry; -import org.neo4j.driver.v1.util.Function; - public abstract class Format { private Format() @@ -31,8 +29,8 @@ private Format() throw new UnsupportedOperationException(); } - public static String formatPairs( Function printValue, - Map entries ) + // formats map using ':' as key-value separator instead of default '=' + public static String formatPairs( Map entries ) { Iterator> iterator = entries.entrySet().iterator(); switch ( entries.size() ) { @@ -41,19 +39,19 @@ public static String formatPairs( Function printValue, case 1: { - return String.format( "{%s}", keyValueString( iterator.next(), printValue ) ); + return String.format( "{%s}", keyValueString( iterator.next() ) ); } default: { StringBuilder builder = new StringBuilder(); builder.append( "{" ); - builder.append( keyValueString( iterator.next(), printValue ) ); + builder.append( keyValueString( iterator.next() ) ); while ( iterator.hasNext() ) { builder.append( ',' ); builder.append( ' ' ); - builder.append( keyValueString( iterator.next(), printValue ) ); + builder.append( keyValueString( iterator.next() ) ); } builder.append( "}" ); return builder.toString(); @@ -61,33 +59,8 @@ public static String formatPairs( Function printValue, } } - private static String keyValueString( Entry entry, Function printValue ) - { - return String.format("%s: %s", entry.getKey(), printValue.apply( entry.getValue() )); - } - - public static String formatElements( Function printValue, V[] elements ) + private static String keyValueString( Entry entry ) { - int elementCount = elements.length; - switch ( elementCount ) { - case 0: - return "[]"; - - case 1: - return String.format( "[%s]", printValue.apply( elements[0] ) ); - - default: - StringBuilder builder = new StringBuilder(); - builder.append("["); - builder.append( printValue.apply( elements[0] ) ); - for (int i = 1; i < elementCount; i++ ) - { - builder.append( ',' ); - builder.append( ' ' ); - builder.append( printValue.apply( elements[i] ) ); - } - builder.append("]"); - return builder.toString(); - } + return String.format( "%s: %s", entry.getKey(), String.valueOf( entry.getValue() ) ); } } diff --git a/driver/src/main/java/org/neo4j/driver/internal/value/BooleanValue.java b/driver/src/main/java/org/neo4j/driver/internal/value/BooleanValue.java index 85b36d64d2..02d80202cc 100644 --- a/driver/src/main/java/org/neo4j/driver/internal/value/BooleanValue.java +++ b/driver/src/main/java/org/neo4j/driver/internal/value/BooleanValue.java @@ -21,7 +21,7 @@ import org.neo4j.driver.internal.types.InternalTypeSystem; import org.neo4j.driver.v1.types.Type; -public abstract class BooleanValue extends ScalarValueAdapter +public abstract class BooleanValue extends ValueAdapter { private BooleanValue() { @@ -86,7 +86,7 @@ public boolean equals( Object obj ) } @Override - public String asLiteralString() + public String toString() { return "TRUE"; } @@ -126,7 +126,7 @@ public boolean equals( Object obj ) } @Override - public String asLiteralString() + public String toString() { return "FALSE"; } diff --git a/driver/src/main/java/org/neo4j/driver/internal/value/BytesValue.java b/driver/src/main/java/org/neo4j/driver/internal/value/BytesValue.java index 8ac08d6e71..929e0fd8e1 100644 --- a/driver/src/main/java/org/neo4j/driver/internal/value/BytesValue.java +++ b/driver/src/main/java/org/neo4j/driver/internal/value/BytesValue.java @@ -18,11 +18,11 @@ */ package org.neo4j.driver.internal.value; +import java.util.Arrays; + import org.neo4j.driver.internal.types.InternalTypeSystem; import org.neo4j.driver.v1.types.Type; -import java.util.Arrays; - public class BytesValue extends ValueAdapter { private final byte[] val; @@ -89,7 +89,7 @@ public int hashCode() } @Override - public String toString(Format valueFormat) + public String toString() { StringBuilder s = new StringBuilder("#"); for (byte b : val) @@ -100,9 +100,6 @@ public String toString(Format valueFormat) } s.append(Integer.toHexString(b)); } - return maybeWithType( - valueFormat.includeType(), - s.toString() - ); + return s.toString(); } } diff --git a/driver/src/main/java/org/neo4j/driver/internal/value/DateTimeValue.java b/driver/src/main/java/org/neo4j/driver/internal/value/DateTimeValue.java new file mode 100644 index 0000000000..c33a46ffa1 --- /dev/null +++ b/driver/src/main/java/org/neo4j/driver/internal/value/DateTimeValue.java @@ -0,0 +1,44 @@ +/* + * Copyright (c) 2002-2018 "Neo Technology," + * Network Engine for Objects in Lund AB [http://neotechnology.com] + * + * This file is part of Neo4j. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.neo4j.driver.internal.value; + +import java.time.ZonedDateTime; + +import org.neo4j.driver.internal.types.InternalTypeSystem; +import org.neo4j.driver.v1.types.Type; + +public class DateTimeValue extends ObjectValueAdapter +{ + public DateTimeValue( ZonedDateTime zonedDateTime ) + { + super( zonedDateTime ); + } + + @Override + public ZonedDateTime asZonedDateTime() + { + return asObject(); + } + + @Override + public Type type() + { + return InternalTypeSystem.TYPE_SYSTEM.DATE_TIME(); + } +} diff --git a/driver/src/main/java/org/neo4j/driver/internal/value/ScalarValueAdapter.java b/driver/src/main/java/org/neo4j/driver/internal/value/DateValue.java similarity index 65% rename from driver/src/main/java/org/neo4j/driver/internal/value/ScalarValueAdapter.java rename to driver/src/main/java/org/neo4j/driver/internal/value/DateValue.java index 3609fccad6..29a02bb867 100644 --- a/driver/src/main/java/org/neo4j/driver/internal/value/ScalarValueAdapter.java +++ b/driver/src/main/java/org/neo4j/driver/internal/value/DateValue.java @@ -18,14 +18,27 @@ */ package org.neo4j.driver.internal.value; -public abstract class ScalarValueAdapter extends ValueAdapter +import java.time.LocalDate; + +import org.neo4j.driver.internal.types.InternalTypeSystem; +import org.neo4j.driver.v1.types.Type; + +public class DateValue extends ObjectValueAdapter { + public DateValue( LocalDate date ) + { + super( date ); + } + @Override - public abstract String asLiteralString(); + public LocalDate asLocalDate() + { + return asObject(); + } @Override - public String toString( Format valueFormat ) + public Type type() { - return maybeWithType( valueFormat.includeType(), asLiteralString() ); + return InternalTypeSystem.TYPE_SYSTEM.DATE(); } } diff --git a/driver/src/main/java/org/neo4j/driver/internal/value/DurationValue.java b/driver/src/main/java/org/neo4j/driver/internal/value/DurationValue.java new file mode 100644 index 0000000000..4622f57bd5 --- /dev/null +++ b/driver/src/main/java/org/neo4j/driver/internal/value/DurationValue.java @@ -0,0 +1,43 @@ +/* + * Copyright (c) 2002-2018 "Neo Technology," + * Network Engine for Objects in Lund AB [http://neotechnology.com] + * + * This file is part of Neo4j. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.neo4j.driver.internal.value; + +import org.neo4j.driver.internal.types.InternalTypeSystem; +import org.neo4j.driver.v1.types.IsoDuration; +import org.neo4j.driver.v1.types.Type; + +public class DurationValue extends ObjectValueAdapter +{ + public DurationValue( IsoDuration duration ) + { + super( duration ); + } + + @Override + public IsoDuration asIsoDuration() + { + return asObject(); + } + + @Override + public Type type() + { + return InternalTypeSystem.TYPE_SYSTEM.DURATION(); + } +} diff --git a/driver/src/main/java/org/neo4j/driver/internal/value/EntityValueAdapter.java b/driver/src/main/java/org/neo4j/driver/internal/value/EntityValueAdapter.java index fbcace4ab4..976f00a1c5 100644 --- a/driver/src/main/java/org/neo4j/driver/internal/value/EntityValueAdapter.java +++ b/driver/src/main/java/org/neo4j/driver/internal/value/EntityValueAdapter.java @@ -24,7 +24,7 @@ import org.neo4j.driver.v1.types.Entity; import org.neo4j.driver.v1.util.Function; -public abstract class EntityValueAdapter extends GraphValueAdapter +public abstract class EntityValueAdapter extends ObjectValueAdapter { protected EntityValueAdapter( V adapted ) { diff --git a/driver/src/main/java/org/neo4j/driver/internal/value/FloatValue.java b/driver/src/main/java/org/neo4j/driver/internal/value/FloatValue.java index fe6094b869..255cf8d913 100644 --- a/driver/src/main/java/org/neo4j/driver/internal/value/FloatValue.java +++ b/driver/src/main/java/org/neo4j/driver/internal/value/FloatValue.java @@ -19,8 +19,8 @@ package org.neo4j.driver.internal.value; import org.neo4j.driver.internal.types.InternalTypeSystem; -import org.neo4j.driver.v1.types.Type; import org.neo4j.driver.v1.exceptions.value.LossyCoercion; +import org.neo4j.driver.v1.types.Type; public class FloatValue extends NumberValueAdapter { @@ -109,7 +109,7 @@ public int hashCode() } @Override - public String asLiteralString() + public String toString() { return Double.toString( val ); } diff --git a/driver/src/main/java/org/neo4j/driver/internal/value/IntegerValue.java b/driver/src/main/java/org/neo4j/driver/internal/value/IntegerValue.java index cbed10d8f7..1646e863f2 100644 --- a/driver/src/main/java/org/neo4j/driver/internal/value/IntegerValue.java +++ b/driver/src/main/java/org/neo4j/driver/internal/value/IntegerValue.java @@ -19,8 +19,8 @@ package org.neo4j.driver.internal.value; import org.neo4j.driver.internal.types.InternalTypeSystem; -import org.neo4j.driver.v1.types.Type; import org.neo4j.driver.v1.exceptions.value.LossyCoercion; +import org.neo4j.driver.v1.types.Type; public class IntegerValue extends NumberValueAdapter { @@ -78,7 +78,7 @@ public float asFloat() } @Override - public String asLiteralString() + public String toString() { return Long.toString( val ); } diff --git a/driver/src/main/java/org/neo4j/driver/internal/value/InternalValue.java b/driver/src/main/java/org/neo4j/driver/internal/value/InternalValue.java index a5a000c1a3..e186c72ef9 100644 --- a/driver/src/main/java/org/neo4j/driver/internal/value/InternalValue.java +++ b/driver/src/main/java/org/neo4j/driver/internal/value/InternalValue.java @@ -20,34 +20,9 @@ import org.neo4j.driver.internal.AsValue; import org.neo4j.driver.internal.types.TypeConstructor; -import org.neo4j.driver.v1.util.Function; import org.neo4j.driver.v1.Value; public interface InternalValue extends Value, AsValue { TypeConstructor typeConstructor(); - - String toString( Format valueFormat ); - - enum Format implements Function - { - VALUE_ONLY, - VALUE_WITH_TYPE; - - public boolean includeType() - { - return this != VALUE_ONLY; - } - - public Format inner() - { - return VALUE_ONLY; - } - - @Override - public String apply( Value value ) - { - return ( (InternalValue) value ).toString( this ); - } - } } diff --git a/driver/src/main/java/org/neo4j/driver/internal/value/ListValue.java b/driver/src/main/java/org/neo4j/driver/internal/value/ListValue.java index 4db362b6e4..edfcc8670f 100644 --- a/driver/src/main/java/org/neo4j/driver/internal/value/ListValue.java +++ b/driver/src/main/java/org/neo4j/driver/internal/value/ListValue.java @@ -24,13 +24,11 @@ import org.neo4j.driver.internal.types.InternalTypeSystem; import org.neo4j.driver.internal.util.Extract; -import org.neo4j.driver.v1.util.Function; -import org.neo4j.driver.v1.types.Type; import org.neo4j.driver.v1.Value; import org.neo4j.driver.v1.Values; +import org.neo4j.driver.v1.types.Type; +import org.neo4j.driver.v1.util.Function; -import static org.neo4j.driver.internal.util.Format.formatElements; -import static org.neo4j.driver.internal.value.InternalValue.Format.VALUE_ONLY; import static org.neo4j.driver.v1.Values.ofObject; public class ListValue extends ValueAdapter @@ -115,12 +113,6 @@ public void remove() }; } - @Override - public String asLiteralString() - { - return toString( VALUE_ONLY ); - } - @Override public Type type() { @@ -128,12 +120,9 @@ public Type type() } @Override - public String toString( Format valueFormat ) + public String toString() { - return maybeWithType( - valueFormat.includeType(), - formatElements( valueFormat.inner(), values ) - ); + return Arrays.toString( values ); } @Override diff --git a/driver/src/main/java/org/neo4j/driver/internal/value/LocalDateTimeValue.java b/driver/src/main/java/org/neo4j/driver/internal/value/LocalDateTimeValue.java new file mode 100644 index 0000000000..9fc569135d --- /dev/null +++ b/driver/src/main/java/org/neo4j/driver/internal/value/LocalDateTimeValue.java @@ -0,0 +1,44 @@ +/* + * Copyright (c) 2002-2018 "Neo Technology," + * Network Engine for Objects in Lund AB [http://neotechnology.com] + * + * This file is part of Neo4j. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.neo4j.driver.internal.value; + +import java.time.LocalDateTime; + +import org.neo4j.driver.internal.types.InternalTypeSystem; +import org.neo4j.driver.v1.types.Type; + +public class LocalDateTimeValue extends ObjectValueAdapter +{ + public LocalDateTimeValue( LocalDateTime localDateTime ) + { + super( localDateTime ); + } + + @Override + public LocalDateTime asLocalDateTime() + { + return asObject(); + } + + @Override + public Type type() + { + return InternalTypeSystem.TYPE_SYSTEM.LOCAL_DATE_TIME(); + } +} diff --git a/driver/src/main/java/org/neo4j/driver/internal/value/LocalTimeValue.java b/driver/src/main/java/org/neo4j/driver/internal/value/LocalTimeValue.java new file mode 100644 index 0000000000..505a7af287 --- /dev/null +++ b/driver/src/main/java/org/neo4j/driver/internal/value/LocalTimeValue.java @@ -0,0 +1,44 @@ +/* + * Copyright (c) 2002-2018 "Neo Technology," + * Network Engine for Objects in Lund AB [http://neotechnology.com] + * + * This file is part of Neo4j. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.neo4j.driver.internal.value; + +import java.time.LocalTime; + +import org.neo4j.driver.internal.types.InternalTypeSystem; +import org.neo4j.driver.v1.types.Type; + +public class LocalTimeValue extends ObjectValueAdapter +{ + public LocalTimeValue( LocalTime time ) + { + super( time ); + } + + @Override + public LocalTime asLocalTime() + { + return asObject(); + } + + @Override + public Type type() + { + return InternalTypeSystem.TYPE_SYSTEM.LOCAL_TIME(); + } +} diff --git a/driver/src/main/java/org/neo4j/driver/internal/value/MapValue.java b/driver/src/main/java/org/neo4j/driver/internal/value/MapValue.java index 7d08b85922..8152b0e63f 100644 --- a/driver/src/main/java/org/neo4j/driver/internal/value/MapValue.java +++ b/driver/src/main/java/org/neo4j/driver/internal/value/MapValue.java @@ -28,9 +28,8 @@ import org.neo4j.driver.v1.util.Function; import static org.neo4j.driver.internal.util.Format.formatPairs; -import static org.neo4j.driver.internal.value.InternalValue.Format.VALUE_ONLY; -import static org.neo4j.driver.v1.Values.ofValue; import static org.neo4j.driver.v1.Values.ofObject; +import static org.neo4j.driver.v1.Values.ofValue; public class MapValue extends ValueAdapter { @@ -107,18 +106,9 @@ public Value get( String key ) } @Override - public String asLiteralString() - { - return toString( VALUE_ONLY ); - } - - @Override - public String toString( Format valueFormat ) + public String toString() { - return maybeWithType( - valueFormat.includeType(), - formatPairs( valueFormat.inner(), asMap( ofValue()) ) - ); + return formatPairs( asMap( ofValue() ) ); } @Override diff --git a/driver/src/main/java/org/neo4j/driver/internal/value/NullValue.java b/driver/src/main/java/org/neo4j/driver/internal/value/NullValue.java index ff3f34ab9f..cc5fb3c839 100644 --- a/driver/src/main/java/org/neo4j/driver/internal/value/NullValue.java +++ b/driver/src/main/java/org/neo4j/driver/internal/value/NullValue.java @@ -19,12 +19,12 @@ package org.neo4j.driver.internal.value; import org.neo4j.driver.internal.types.InternalTypeSystem; -import org.neo4j.driver.v1.types.Type; import org.neo4j.driver.v1.Value; +import org.neo4j.driver.v1.types.Type; -public final class NullValue extends ScalarValueAdapter +public final class NullValue extends ValueAdapter { - public static Value NULL = new NullValue(); + public static final Value NULL = new NullValue(); private NullValue() { @@ -68,7 +68,7 @@ public int hashCode() } @Override - public String asLiteralString() + public String toString() { return "NULL"; } diff --git a/driver/src/main/java/org/neo4j/driver/internal/value/NumberValueAdapter.java b/driver/src/main/java/org/neo4j/driver/internal/value/NumberValueAdapter.java index b4abf426e2..1a190e842d 100644 --- a/driver/src/main/java/org/neo4j/driver/internal/value/NumberValueAdapter.java +++ b/driver/src/main/java/org/neo4j/driver/internal/value/NumberValueAdapter.java @@ -18,7 +18,7 @@ */ package org.neo4j.driver.internal.value; -public abstract class NumberValueAdapter extends ScalarValueAdapter +public abstract class NumberValueAdapter extends ValueAdapter { @Override public final V asObject() diff --git a/driver/src/main/java/org/neo4j/driver/internal/value/GraphValueAdapter.java b/driver/src/main/java/org/neo4j/driver/internal/value/ObjectValueAdapter.java similarity index 79% rename from driver/src/main/java/org/neo4j/driver/internal/value/GraphValueAdapter.java rename to driver/src/main/java/org/neo4j/driver/internal/value/ObjectValueAdapter.java index 1c53e80269..d67d26a808 100644 --- a/driver/src/main/java/org/neo4j/driver/internal/value/GraphValueAdapter.java +++ b/driver/src/main/java/org/neo4j/driver/internal/value/ObjectValueAdapter.java @@ -18,13 +18,15 @@ */ package org.neo4j.driver.internal.value; +import java.util.Objects; + import static java.lang.String.format; -public abstract class GraphValueAdapter extends ValueAdapter +public abstract class ObjectValueAdapter extends ValueAdapter { private final V adapted; - protected GraphValueAdapter( V adapted ) + protected ObjectValueAdapter( V adapted ) { if ( adapted == null ) { @@ -34,17 +36,11 @@ protected GraphValueAdapter( V adapted ) } @Override - public V asObject() + public final V asObject() { return adapted; } - @Override - public String toString( Format valueFormat ) - { - return maybeWithType( valueFormat.includeType(), adapted.toString() ); - } - @Override public boolean equals( Object o ) { @@ -56,10 +52,8 @@ public boolean equals( Object o ) { return false; } - - GraphValueAdapter values = (GraphValueAdapter) o; - return adapted.equals( values.adapted ); - + ObjectValueAdapter that = (ObjectValueAdapter) o; + return Objects.equals( adapted, that.adapted ); } @Override @@ -67,4 +61,10 @@ public int hashCode() { return adapted.hashCode(); } + + @Override + public String toString() + { + return adapted.toString(); + } } diff --git a/driver/src/main/java/org/neo4j/driver/internal/value/PathValue.java b/driver/src/main/java/org/neo4j/driver/internal/value/PathValue.java index f288a50ab3..eee940fbb8 100644 --- a/driver/src/main/java/org/neo4j/driver/internal/value/PathValue.java +++ b/driver/src/main/java/org/neo4j/driver/internal/value/PathValue.java @@ -22,13 +22,14 @@ import org.neo4j.driver.v1.types.Path; import org.neo4j.driver.v1.types.Type; -public class PathValue extends GraphValueAdapter +public class PathValue extends ObjectValueAdapter { public PathValue( Path adapted ) { super( adapted ); } + @Override public Path asPath() { return asObject(); diff --git a/driver/src/main/java/org/neo4j/driver/internal/value/Point2DValue.java b/driver/src/main/java/org/neo4j/driver/internal/value/Point2DValue.java index 75afa39d82..c0c1aad248 100644 --- a/driver/src/main/java/org/neo4j/driver/internal/value/Point2DValue.java +++ b/driver/src/main/java/org/neo4j/driver/internal/value/Point2DValue.java @@ -22,31 +22,17 @@ import org.neo4j.driver.v1.types.Point2D; import org.neo4j.driver.v1.types.Type; -public class Point2DValue extends ValueAdapter +public class Point2DValue extends ObjectValueAdapter { - private final Point2D point; - public Point2DValue( Point2D point ) { - this.point = point; + super( point ); } @Override public Point2D asPoint2D() { - return point; - } - - @Override - public Object asObject() - { - return point; - } - - @Override - public String toString( Format valueFormat ) - { - return maybeWithType( valueFormat.includeType(), point.toString() ); + return asObject(); } @Override diff --git a/driver/src/main/java/org/neo4j/driver/internal/value/Point3DValue.java b/driver/src/main/java/org/neo4j/driver/internal/value/Point3DValue.java index 4b49f58f1e..12d9671052 100644 --- a/driver/src/main/java/org/neo4j/driver/internal/value/Point3DValue.java +++ b/driver/src/main/java/org/neo4j/driver/internal/value/Point3DValue.java @@ -22,31 +22,17 @@ import org.neo4j.driver.v1.types.Point3D; import org.neo4j.driver.v1.types.Type; -public class Point3DValue extends ValueAdapter +public class Point3DValue extends ObjectValueAdapter { - private final Point3D point; - public Point3DValue( Point3D point ) { - this.point = point; + super( point ); } @Override public Point3D asPoint3D() { - return point; - } - - @Override - public Object asObject() - { - return point; - } - - @Override - public String toString( Format valueFormat ) - { - return maybeWithType( valueFormat.includeType(), point.toString() ); + return asObject(); } @Override diff --git a/driver/src/main/java/org/neo4j/driver/internal/value/StringValue.java b/driver/src/main/java/org/neo4j/driver/internal/value/StringValue.java index 19c490d2f6..42b8ba6083 100644 --- a/driver/src/main/java/org/neo4j/driver/internal/value/StringValue.java +++ b/driver/src/main/java/org/neo4j/driver/internal/value/StringValue.java @@ -18,10 +18,12 @@ */ package org.neo4j.driver.internal.value; +import java.util.Objects; + import org.neo4j.driver.internal.types.InternalTypeSystem; import org.neo4j.driver.v1.types.Type; -public class StringValue extends ScalarValueAdapter +public class StringValue extends ValueAdapter { private final String val; @@ -59,7 +61,7 @@ public String asString() } @Override - public String asLiteralString() + public String toString() { return String.format( "\"%s\"", val.replace( "\"", "\\\"" ) ); } @@ -70,7 +72,6 @@ public Type type() return InternalTypeSystem.TYPE_SYSTEM.STRING(); } - @SuppressWarnings("StringEquality") @Override public boolean equals( Object o ) { @@ -82,9 +83,8 @@ public boolean equals( Object o ) { return false; } - - StringValue values = (StringValue) o; - return val == values.val || val.equals( values.val ); + StringValue that = (StringValue) o; + return Objects.equals( val, that.val ); } @Override diff --git a/driver/src/main/java/org/neo4j/driver/internal/value/TimeValue.java b/driver/src/main/java/org/neo4j/driver/internal/value/TimeValue.java new file mode 100644 index 0000000000..69289418b0 --- /dev/null +++ b/driver/src/main/java/org/neo4j/driver/internal/value/TimeValue.java @@ -0,0 +1,44 @@ +/* + * Copyright (c) 2002-2018 "Neo Technology," + * Network Engine for Objects in Lund AB [http://neotechnology.com] + * + * This file is part of Neo4j. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.neo4j.driver.internal.value; + +import java.time.OffsetTime; + +import org.neo4j.driver.internal.types.InternalTypeSystem; +import org.neo4j.driver.v1.types.Type; + +public class TimeValue extends ObjectValueAdapter +{ + public TimeValue( OffsetTime time ) + { + super( time ); + } + + @Override + public OffsetTime asOffsetTime() + { + return asObject(); + } + + @Override + public Type type() + { + return InternalTypeSystem.TYPE_SYSTEM.TIME(); + } +} diff --git a/driver/src/main/java/org/neo4j/driver/internal/value/ValueAdapter.java b/driver/src/main/java/org/neo4j/driver/internal/value/ValueAdapter.java index 7e976e92b6..fafa7b3663 100644 --- a/driver/src/main/java/org/neo4j/driver/internal/value/ValueAdapter.java +++ b/driver/src/main/java/org/neo4j/driver/internal/value/ValueAdapter.java @@ -18,6 +18,11 @@ */ package org.neo4j.driver.internal.value; +import java.time.LocalDate; +import java.time.LocalDateTime; +import java.time.LocalTime; +import java.time.OffsetTime; +import java.time.ZonedDateTime; import java.util.List; import java.util.Map; @@ -29,6 +34,7 @@ import org.neo4j.driver.v1.exceptions.value.Uncoercible; import org.neo4j.driver.v1.exceptions.value.Unsizable; import org.neo4j.driver.v1.types.Entity; +import org.neo4j.driver.v1.types.IsoDuration; import org.neo4j.driver.v1.types.Node; import org.neo4j.driver.v1.types.Path; import org.neo4j.driver.v1.types.Point2D; @@ -37,9 +43,7 @@ import org.neo4j.driver.v1.types.Type; import org.neo4j.driver.v1.util.Function; -import static java.lang.String.format; import static java.util.Collections.emptyList; -import static org.neo4j.driver.internal.value.InternalValue.Format.VALUE_ONLY; import static org.neo4j.driver.v1.Values.ofObject; import static org.neo4j.driver.v1.Values.ofValue; @@ -87,11 +91,6 @@ public String asString() throw new Uncoercible( type().name(), "Java String" ); } - public String asLiteralString() - { - throw new Uncoercible( type().name(), "Java String representation of Cypher literal" ); - } - @Override public long asLong() { @@ -188,6 +187,42 @@ public Relationship asRelationship() throw new Uncoercible( type().name(), "Relationship" ); } + @Override + public LocalDate asLocalDate() + { + throw new Uncoercible( type().name(), "LocalDate" ); + } + + @Override + public OffsetTime asOffsetTime() + { + throw new Uncoercible( type().name(), "OffsetTime" ); + } + + @Override + public LocalTime asLocalTime() + { + throw new Uncoercible( type().name(), "LocalTime" ); + } + + @Override + public LocalDateTime asLocalDateTime() + { + throw new Uncoercible( type().name(), "LocalDateTime" ); + } + + @Override + public ZonedDateTime asZonedDateTime() + { + throw new Uncoercible( type().name(), "ZonedDateTime" ); + } + + @Override + public IsoDuration asIsoDuration() + { + throw new Uncoercible( type().name(), "Duration" ); + } + @Override public Point2D asPoint2D() { @@ -242,26 +277,23 @@ public Iterable values( Function mapFunction ) throw new NotMultiValued( type().name() + " is not iterable" ); } - public String toString() + @Override + public final TypeConstructor typeConstructor() { - return toString( VALUE_ONLY ); + return ( (TypeRepresentation) type() ).constructor(); } - protected String maybeWithType( boolean includeType, String text ) - { - return includeType ? withType( text ) : text; - } + // Force implementation + @Override + public abstract boolean equals( Object obj ); - private String withType( String text ) - { - return format( "%s :: %s", text, type().name() ); - } + // Force implementation + @Override + public abstract int hashCode(); + // Force implementation @Override - public final TypeConstructor typeConstructor() - { - return ( (TypeRepresentation) type() ).constructor(); - } + public abstract String toString(); } diff --git a/driver/src/main/java/org/neo4j/driver/v1/Value.java b/driver/src/main/java/org/neo4j/driver/v1/Value.java index f974285711..ce879759d3 100644 --- a/driver/src/main/java/org/neo4j/driver/v1/Value.java +++ b/driver/src/main/java/org/neo4j/driver/v1/Value.java @@ -18,6 +18,11 @@ */ package org.neo4j.driver.v1; +import java.time.LocalDate; +import java.time.LocalDateTime; +import java.time.LocalTime; +import java.time.OffsetTime; +import java.time.ZonedDateTime; import java.util.List; import java.util.Map; @@ -25,6 +30,7 @@ import org.neo4j.driver.v1.exceptions.value.LossyCoercion; import org.neo4j.driver.v1.exceptions.value.Uncoercible; import org.neo4j.driver.v1.types.Entity; +import org.neo4j.driver.v1.types.IsoDuration; import org.neo4j.driver.v1.types.MapAccessor; import org.neo4j.driver.v1.types.MapAccessorWithDefaultValue; import org.neo4j.driver.v1.types.Node; @@ -291,19 +297,60 @@ public interface Value extends MapAccessor, MapAccessorWithDefaultValue */ Path asPath(); + /** + * @return the value as a {@link LocalDate}, if possible. + * @throws Uncoercible if value types are incompatible. + */ + LocalDate asLocalDate(); + + /** + * @return the value as a {@link OffsetTime}, if possible. + * @throws Uncoercible if value types are incompatible. + */ + OffsetTime asOffsetTime(); + + /** + * @return the value as a {@link LocalTime}, if possible. + * @throws Uncoercible if value types are incompatible. + */ + LocalTime asLocalTime(); + + /** + * @return the value as a {@link LocalDateTime}, if possible. + * @throws Uncoercible if value types are incompatible. + */ + LocalDateTime asLocalDateTime(); + + /** + * @return the value as a {@link ZonedDateTime}, if possible. + * @throws Uncoercible if value types are incompatible. + */ + ZonedDateTime asZonedDateTime(); + + /** + * @return the value as a {@link IsoDuration}, if possible. + * @throws Uncoercible if value types are incompatible. + */ + IsoDuration asIsoDuration(); + + /** + * @return the value as a {@link Point2D}, if possible. + * @throws Uncoercible if value types are incompatible. + */ Point2D asPoint2D(); + /** + * @return the value as a {@link Point3D}, if possible. + * @throws Uncoercible if value types are incompatible. + */ Point3D asPoint3D(); - // Force implementation @Override boolean equals( Object other ); - // Force implementation @Override int hashCode(); - //Force implementation @Override String toString(); } diff --git a/driver/src/main/java/org/neo4j/driver/v1/Values.java b/driver/src/main/java/org/neo4j/driver/v1/Values.java index 96572329fe..40956e103c 100644 --- a/driver/src/main/java/org/neo4j/driver/v1/Values.java +++ b/driver/src/main/java/org/neo4j/driver/v1/Values.java @@ -18,6 +18,13 @@ */ package org.neo4j.driver.v1; +import java.time.Duration; +import java.time.LocalDate; +import java.time.LocalDateTime; +import java.time.LocalTime; +import java.time.OffsetTime; +import java.time.Period; +import java.time.ZonedDateTime; import java.util.ArrayList; import java.util.Arrays; import java.util.Collection; @@ -28,20 +35,29 @@ import java.util.Map; import org.neo4j.driver.internal.AsValue; +import org.neo4j.driver.internal.InternalIsoDuration; import org.neo4j.driver.internal.InternalPoint2D; import org.neo4j.driver.internal.InternalPoint3D; import org.neo4j.driver.internal.value.BooleanValue; import org.neo4j.driver.internal.value.BytesValue; +import org.neo4j.driver.internal.value.DateTimeValue; +import org.neo4j.driver.internal.value.DateValue; +import org.neo4j.driver.internal.value.DurationValue; import org.neo4j.driver.internal.value.FloatValue; import org.neo4j.driver.internal.value.IntegerValue; import org.neo4j.driver.internal.value.ListValue; +import org.neo4j.driver.internal.value.LocalDateTimeValue; +import org.neo4j.driver.internal.value.LocalTimeValue; import org.neo4j.driver.internal.value.MapValue; import org.neo4j.driver.internal.value.NullValue; import org.neo4j.driver.internal.value.Point2DValue; import org.neo4j.driver.internal.value.Point3DValue; import org.neo4j.driver.internal.value.StringValue; +import org.neo4j.driver.internal.value.TimeValue; import org.neo4j.driver.v1.exceptions.ClientException; import org.neo4j.driver.v1.types.Entity; +import org.neo4j.driver.v1.types.IsoDuration; +import org.neo4j.driver.v1.types.MapAccessor; import org.neo4j.driver.v1.types.Node; import org.neo4j.driver.v1.types.Path; import org.neo4j.driver.v1.types.Point2D; @@ -55,7 +71,7 @@ /** * Utility for wrapping regular Java types and exposing them as {@link Value} * objects, and vice versa. - * + *

* The long set of {@code ofXXX} methods in this class are meant to be used as * arguments for methods like {@link Value#asList(Function)}, {@link Value#asMap(Function)}, * {@link Record#asMap(Function)} and so on. @@ -72,12 +88,12 @@ private Values() throw new UnsupportedOperationException(); } - @SuppressWarnings("unchecked") + @SuppressWarnings( "unchecked" ) public static Value value( Object value ) { if ( value == null ) { return NullValue.NULL; } - if ( value instanceof AsValue ) { return ( (AsValue) value ).asValue(); } + if ( value instanceof AsValue ) { return ((AsValue) value).asValue(); } if ( value instanceof Boolean ) { return value( (boolean) value ); } if ( value instanceof String ) { return value( (String) value ); } if ( value instanceof Character ) { return value( (char) value ); } @@ -87,9 +103,19 @@ public static Value value( Object value ) if ( value instanceof Integer ) { return value( (int) value ); } if ( value instanceof Double ) { return value( (double) value ); } if ( value instanceof Float ) { return value( (float) value ); } + if ( value instanceof LocalDate ) { return value( (LocalDate) value ); } + if ( value instanceof OffsetTime ) { return value( (OffsetTime) value ); } + if ( value instanceof LocalTime ) { return value( (LocalTime) value ); } + if ( value instanceof LocalDateTime ) { return value( (LocalDateTime) value ); } + if ( value instanceof ZonedDateTime ) { return value( (ZonedDateTime) value ); } + if ( value instanceof IsoDuration ) { return value( (IsoDuration) value ); } + if ( value instanceof Period ) { return value( (Period) value ); } + if ( value instanceof Duration ) { return value( (Duration) value ); } + if ( value instanceof Point2D ) { return value( (Point2D) value ); } + if ( value instanceof Point3D ) { return value( (Point3D) value ); } if ( value instanceof List ) { return value( (List) value ); } - if ( value instanceof Map ) { return value( (Map) value ); } + if ( value instanceof Map ) { return value( (Map) value ); } if ( value instanceof Iterable ) { return value( (Iterable) value ); } if ( value instanceof Iterator ) { return value( (Iterator) value ); } @@ -101,12 +127,11 @@ public static Value value( Object value ) if ( value instanceof double[] ) { return value( (double[]) value ); } if ( value instanceof float[] ) { return value( (float[]) value ); } if ( value instanceof Value[] ) { return value( (Value[]) value ); } - if ( value instanceof Object[] ) { return value( Arrays.asList( (Object[]) value )); } + if ( value instanceof Object[] ) { return value( Arrays.asList( (Object[]) value ) ); } throw new ClientException( "Unable to convert " + value.getClass().getName() + " to Neo4j Value." ); } - public static Value[] values( final Object... input ) { Value[] values = new Value[input.length]; @@ -226,7 +251,10 @@ public static Value value( Iterator val ) return new ListValue( values.toArray( new Value[values.size()] ) ); } - public static Value value (final char val ) { return new StringValue( String.valueOf( val ) ); } + public static Value value( final char val ) + { + return new StringValue( String.valueOf( val ) ); + } public static Value value( final String val ) { @@ -263,14 +291,69 @@ public static Value value( final Map val ) return new MapValue( asValues ); } + public static Value value( LocalDate localDate ) + { + return new DateValue( localDate ); + } + + public static Value value( OffsetTime offsetTime ) + { + return new TimeValue( offsetTime ); + } + + public static Value value( LocalTime localTime ) + { + return new LocalTimeValue( localTime ); + } + + public static Value value( LocalDateTime localDateTime ) + { + return new LocalDateTimeValue( localDateTime ); + } + + public static Value value( ZonedDateTime zonedDateTime ) + { + return new DateTimeValue( zonedDateTime ); + } + + public static Value value( Period period ) + { + return value( new InternalIsoDuration( period ) ); + } + + public static Value value( Duration duration ) + { + return value( new InternalIsoDuration( duration ) ); + } + + public static Value isoDuration( long months, long days, long seconds, long nanoseconds ) + { + return value( new InternalIsoDuration( months, days, seconds, nanoseconds ) ); + } + + private static Value value( IsoDuration duration ) + { + return new DurationValue( duration ); + } + public static Value point2D( long srid, double x, double y ) { - return new Point2DValue( new InternalPoint2D( srid, x, y ) ); + return value( new InternalPoint2D( srid, x, y ) ); + } + + private static Value value( Point2D point2D ) + { + return new Point2DValue( point2D ); } public static Value point3D( long srid, double x, double y, double z ) { - return new Point3DValue( new InternalPoint3D( srid, x, y, z ) ); + return value( new InternalPoint3D( srid, x, y, z ) ); + } + + private static Value value( Point3D point3D ) + { + return new Point3DValue( point3D ); } /** @@ -279,14 +362,14 @@ public static Value point3D( long srid, double x, double y, double z ) *

* Allowed parameter types are: *

    - *
  • {@link Integer}
  • - *
  • {@link Long}
  • - *
  • {@link Boolean}
  • - *
  • {@link Double}
  • - *
  • {@link Float}
  • - *
  • {@link String}
  • - *
  • {@link Map} with String keys and values being any type in this list
  • - *
  • {@link Collection} of any type in this list
  • + *
  • {@link Integer}
  • + *
  • {@link Long}
  • + *
  • {@link Boolean}
  • + *
  • {@link Double}
  • + *
  • {@link Float}
  • + *
  • {@link String}
  • + *
  • {@link Map} with String keys and values being any type in this list
  • + *
  • {@link Collection} of any type in this list
  • *
* * @param keysAndValues alternating sequence of keys and values @@ -309,39 +392,42 @@ public static Value parameters( Object... keysAndValues ) assertParameter( value ); map.put( keysAndValues[i].toString(), value( value ) ); } - return value(map); + return value( map ); } /** * The identity function for value conversion - returns the value untouched. + * * @return a function that returns the value passed into it - the identity function */ public static Function ofValue() { - return VALUE; + return val -> val; } /** * Converts values to objects using {@link Value#asObject()}. + * * @return a function that returns {@link Value#asObject()} of a {@link Value} */ public static Function ofObject() { - return OBJECT; + return Value::asObject; } /** * Converts values to {@link Number}. + * * @return a function that returns {@link Value#asNumber()} of a {@link Value} */ public static Function ofNumber() { - return NUMBER; + return Value::asNumber; } /** * Converts values to {@link String}. - * + *

* If you want to access a string you've retrieved from the database, this is * the right choice. If you want to print any value for human consumption, for * instance in a log, {@link #ofToString()} is the right choice. @@ -350,16 +436,16 @@ public static Function ofNumber() */ public static Function ofString() { - return STRING; + return Value::asString; } /** * Converts values using {@link Value#toString()}, a human-readable string * description of any value. - * + *

* This is different from {@link #ofString()}, which returns a java * {@link String} value from a database {@link TypeSystem#STRING()}. - * + *

* If you are wanting to print any value for human consumption, this is the * right choice. If you are wanting to access a string value stored in the * database, you should use {@link #ofString()}. @@ -368,298 +454,234 @@ public static Function ofString() */ public static Function ofToString() { - return TO_STRING; + return Value::toString; } /** * Converts values to {@link Integer}. + * * @return a function that returns {@link Value#asInt()} of a {@link Value} */ public static Function ofInteger() { - return INTEGER; + return Value::asInt; } /** * Converts values to {@link Long}. + * * @return a function that returns {@link Value#asLong()} of a {@link Value} */ public static Function ofLong() { - return LONG; + return Value::asLong; } /** * Converts values to {@link Float}. + * * @return a function that returns {@link Value#asFloat()} of a {@link Value} */ public static Function ofFloat() { - return FLOAT; + return Value::asFloat; } /** * Converts values to {@link Double}. + * * @return a function that returns {@link Value#asDouble()} of a {@link Value} */ public static Function ofDouble() { - return DOUBLE; + return Value::asDouble; } /** * Converts values to {@link Boolean}. + * * @return a function that returns {@link Value#asBoolean()} of a {@link Value} */ public static Function ofBoolean() { - return BOOLEAN; + return Value::asBoolean; } /** * Converts values to {@link Map}. + * * @return a function that returns {@link Value#asMap()} of a {@link Value} */ - public static Function> ofMap() + public static Function> ofMap() { - return MAP; + return MapAccessor::asMap; } /** * Converts values to {@link Map}, with the map values further converted using * the provided converter. + * * @param valueConverter converter to use for the values of the map * @param the type of values in the returned map * @return a function that returns {@link Value#asMap(Function)} of a {@link Value} */ - public static Function> ofMap( final Function valueConverter) + public static Function> ofMap( final Function valueConverter ) { - return new Function>() - { - public Map apply( Value val ) - { - return val.asMap(valueConverter); - } - }; + return val -> val.asMap( valueConverter ); } /** * Converts values to {@link Entity}. + * * @return a function that returns {@link Value#asEntity()} of a {@link Value} */ public static Function ofEntity() { - return ENTITY; + return Value::asEntity; } /** * Converts values to {@link Long entity id}. + * * @return a function that returns the id an entity {@link Value} */ - public static Function ofEntityId() + public static Function ofEntityId() { - return ENTITY_ID; + return val -> val.asEntity().id(); } /** * Converts values to {@link Node}. + * * @return a function that returns {@link Value#asNode()} of a {@link Value} */ public static Function ofNode() { - return NODE; + return Value::asNode; } /** * Converts values to {@link Relationship}. + * * @return a function that returns {@link Value#asRelationship()} of a {@link Value} */ public static Function ofRelationship() { - return RELATIONSHIP; + return Value::asRelationship; } /** * Converts values to {@link Path}. + * * @return a function that returns {@link Value#asPath()} of a {@link Value} */ public static Function ofPath() { - return PATH; + return Value::asPath; + } + + /** + * Converts values to {@link LocalDate}. + * + * @return a function that returns {@link Value#asLocalDate()} of a {@link Value} + */ + public static Function ofLocalDate() + { + return Value::asLocalDate; + } + + /** + * Converts values to {@link OffsetTime}. + * + * @return a function that returns {@link Value#asOffsetTime()} of a {@link Value} + */ + public static Function ofOffsetTime() + { + return Value::asOffsetTime; } + /** + * Converts values to {@link LocalTime}. + * + * @return a function that returns {@link Value#asLocalTime()} of a {@link Value} + */ + public static Function ofLocalTime() + { + return Value::asLocalTime; + } + + /** + * Converts values to {@link LocalDateTime}. + * + * @return a function that returns {@link Value#asLocalDateTime()} of a {@link Value} + */ + public static Function ofLocalDateTime() + { + return Value::asLocalDateTime; + } + + /** + * Converts values to {@link ZonedDateTime}. + * + * @return a function that returns {@link Value#asZonedDateTime()} of a {@link Value} + */ + public static Function ofZonedDateTime() + { + return Value::asZonedDateTime; + } + + /** + * Converts values to {@link IsoDuration}. + * + * @return a function that returns {@link Value#asIsoDuration()} of a {@link Value} + */ + public static Function ofIsoDuration() + { + return Value::asIsoDuration; + } + + /** + * Converts values to {@link Point2D}. + * + * @return a function that returns {@link Value#asPoint2D()} of a {@link Value} + */ public static Function ofPoint2D() { - return POINT_2D; + return Value::asPoint2D; } + /** + * Converts values to {@link Point3D}. + * + * @return a function that returns {@link Value#asPoint3D()} of a {@link Value} + */ public static Function ofPoint3D() { - return POINT_3D; + return Value::asPoint3D; } /** * Converts values to {@link List} of {@link Object}. + * * @return a function that returns {@link Value#asList()} of a {@link Value} */ public static Function> ofList() { - return new Function>() - { - @Override - public List apply( Value value ) - { - return value.asList(); - } - }; + return Value::asList; } /** * Converts values to {@link List} of T. + * * @param innerMap converter for the values inside the list * @param the type of values inside the list * @return a function that returns {@link Value#asList(Function)} of a {@link Value} */ - public static Function> ofList( final Function innerMap ) + public static Function> ofList( final Function innerMap ) { - return new Function>() - { - @Override - public List apply( Value value ) - { - return value.asList( innerMap ); - } - }; + return value -> value.asList( innerMap ); } - private static final Function OBJECT = new Function() - { - public Object apply( Value val ) - { - return val.asObject(); - } - }; - private static final Function VALUE = new Function() - { - public Value apply( Value val ) - { - return val; - } - }; - private static final Function NUMBER = new Function() - { - public Number apply( Value val ) - { - return val.asNumber(); - } - }; - private static final Function STRING = new Function() - { - public String apply( Value val ) - { - return val.asString(); - } - }; - - private static final Function TO_STRING = new Function() - { - public String apply( Value val ) - { - return val.toString(); - } - }; - private static final Function INTEGER = new Function() - { - public Integer apply( Value val ) - { - return val.asInt(); - } - }; - private static final Function LONG = new Function() - { - public Long apply( Value val ) - { - return val.asLong(); - } - }; - private static final Function FLOAT = new Function() - { - public Float apply( Value val ) - { - return val.asFloat(); - } - }; - private static final Function DOUBLE = new Function() - { - public Double apply( Value val ) - { - return val.asDouble(); - } - }; - private static final Function BOOLEAN = new Function() - { - public Boolean apply( Value val ) - { - return val.asBoolean(); - } - }; - private static final Function> MAP = new Function>() - { - public Map apply( Value val ) - { - return val.asMap(); - } - }; - private static final Function ENTITY_ID = new Function() - { - public Long apply( Value val ) - { - return val.asEntity().id(); - } - }; - private static final Function ENTITY = new Function() - { - public Entity apply( Value val ) - { - return val.asEntity(); - } - }; - private static final Function NODE = new Function() - { - public Node apply( Value val ) - { - return val.asNode(); - } - }; - private static final Function RELATIONSHIP = new Function() - { - public Relationship apply( Value val ) - { - return val.asRelationship(); - } - }; - private static final Function PATH = new Function() - { - public Path apply( Value val ) - { - return val.asPath(); - } - }; - private static final Function POINT_2D = new Function() - { - public Point2D apply( Value val ) - { - return val.asPoint2D(); - } - }; - private static final Function POINT_3D = new Function() - { - public Point3D apply( Value val ) - { - return val.asPoint3D(); - } - }; - private static void assertParameter( Object value ) { if ( value instanceof Node ) diff --git a/driver/src/main/java/org/neo4j/driver/v1/types/IsoDuration.java b/driver/src/main/java/org/neo4j/driver/v1/types/IsoDuration.java new file mode 100644 index 0000000000..7521d44e09 --- /dev/null +++ b/driver/src/main/java/org/neo4j/driver/v1/types/IsoDuration.java @@ -0,0 +1,61 @@ +/* + * Copyright (c) 2002-2018 "Neo Technology," + * Network Engine for Objects in Lund AB [http://neotechnology.com] + * + * This file is part of Neo4j. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.neo4j.driver.v1.types; + +import java.time.temporal.TemporalAmount; + +import org.neo4j.driver.v1.Values; +import org.neo4j.driver.v1.util.Immutable; + +/** + * Represents temporal amount containing months, days, seconds and nanoseconds of the second. A duration can be negative. + *

+ * Value that represents a duration can be created using {@link Values#isoDuration(long, long, long, long)} method. + */ +@Immutable +public interface IsoDuration extends TemporalAmount +{ + /** + * Retrieve amount of months in this duration. + * + * @return number of months. + */ + long months(); + + /** + * Retrieve amount of days in this duration. + * + * @return number of days. + */ + long days(); + + /** + * Retrieve amount of seconds in this duration. + * + * @return number of seconds. + */ + long seconds(); + + /** + * Retrieve amount of nanoseconds of the second in this duration. + * + * @return number of nanoseconds. + */ + long nanoseconds(); +} diff --git a/driver/src/main/java/org/neo4j/driver/v1/types/Point2D.java b/driver/src/main/java/org/neo4j/driver/v1/types/Point2D.java index a452fe1a23..4a2c14e38d 100644 --- a/driver/src/main/java/org/neo4j/driver/v1/types/Point2D.java +++ b/driver/src/main/java/org/neo4j/driver/v1/types/Point2D.java @@ -18,14 +18,35 @@ */ package org.neo4j.driver.v1.types; -import org.neo4j.driver.v1.util.Experimental; +import org.neo4j.driver.v1.Values; +import org.neo4j.driver.v1.util.Immutable; -@Experimental +/** + * Represents a single two-dimensional point in a particular coordinate reference system. + *

+ * Value that represents a 2D point can be created using {@link Values#point2D(long, double, double)} method. + */ +@Immutable public interface Point2D { + /** + * Retrieve identifier of the coordinate reference system for this point. + * + * @return coordinate reference system identifier. + */ long srid(); + /** + * Retrieve {@code x} coordinate of this point. + * + * @return the {@code x} coordinate value. + */ double x(); + /** + * Retrieve {@code y} coordinate of this point. + * + * @return the {@code y} coordinate value. + */ double y(); } diff --git a/driver/src/main/java/org/neo4j/driver/v1/types/Point3D.java b/driver/src/main/java/org/neo4j/driver/v1/types/Point3D.java index 11b6cfce2d..d5c1f48e94 100644 --- a/driver/src/main/java/org/neo4j/driver/v1/types/Point3D.java +++ b/driver/src/main/java/org/neo4j/driver/v1/types/Point3D.java @@ -18,16 +18,42 @@ */ package org.neo4j.driver.v1.types; -import org.neo4j.driver.v1.util.Experimental; +import org.neo4j.driver.v1.Values; +import org.neo4j.driver.v1.util.Immutable; -@Experimental +/** + * Represents a single tree-dimensional point in a particular coordinate reference system. + *

+ * Value that represents a 3D point can be created using {@link Values#point3D(long, double, double, double)} method. + */ +@Immutable public interface Point3D { + /** + * Retrieve identifier of the coordinate reference system for this point. + * + * @return coordinate reference system identifier. + */ long srid(); + /** + * Retrieve {@code x} coordinate of this point. + * + * @return the {@code x} coordinate value. + */ double x(); + /** + * Retrieve {@code y} coordinate of this point. + * + * @return the {@code y} coordinate value. + */ double y(); + /** + * Retrieve {@code z} coordinate of this point. + * + * @return the {@code z} coordinate value. + */ double z(); } diff --git a/driver/src/main/java/org/neo4j/driver/v1/types/TypeSystem.java b/driver/src/main/java/org/neo4j/driver/v1/types/TypeSystem.java index 3db423d053..c65f4a620b 100644 --- a/driver/src/main/java/org/neo4j/driver/v1/types/TypeSystem.java +++ b/driver/src/main/java/org/neo4j/driver/v1/types/TypeSystem.java @@ -57,5 +57,17 @@ public interface TypeSystem Type POINT_3D(); + Type DATE(); + + Type TIME(); + + Type LOCAL_TIME(); + + Type LOCAL_DATE_TIME(); + + Type DATE_TIME(); + + Type DURATION(); + Type NULL(); } diff --git a/driver/src/test/java/org/neo4j/driver/internal/InternalIsoDurationTest.java b/driver/src/test/java/org/neo4j/driver/internal/InternalIsoDurationTest.java new file mode 100644 index 0000000000..438f9d09dc --- /dev/null +++ b/driver/src/test/java/org/neo4j/driver/internal/InternalIsoDurationTest.java @@ -0,0 +1,158 @@ +/* + * Copyright (c) 2002-2018 "Neo Technology," + * Network Engine for Objects in Lund AB [http://neotechnology.com] + * + * This file is part of Neo4j. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.neo4j.driver.internal; + +import org.junit.Test; + +import java.time.Duration; +import java.time.LocalDateTime; +import java.time.Period; +import java.time.temporal.Temporal; +import java.time.temporal.UnsupportedTemporalTypeException; + +import org.neo4j.driver.v1.types.IsoDuration; + +import static java.time.temporal.ChronoUnit.DAYS; +import static java.time.temporal.ChronoUnit.MONTHS; +import static java.time.temporal.ChronoUnit.NANOS; +import static java.time.temporal.ChronoUnit.SECONDS; +import static java.time.temporal.ChronoUnit.YEARS; +import static java.util.Arrays.asList; +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.fail; + +public class InternalIsoDurationTest +{ + @Test + public void shouldExposeMonths() + { + IsoDuration duration = newDuration( 42, 1, 2, 3 ); + assertEquals( 42, duration.months() ); + assertEquals( 42, duration.get( MONTHS ) ); + } + + @Test + public void shouldExposeDays() + { + IsoDuration duration = newDuration( 1, 42, 2, 3 ); + assertEquals( 42, duration.days() ); + assertEquals( 42, duration.get( DAYS ) ); + } + + @Test + public void shouldExposeSeconds() + { + IsoDuration duration = newDuration( 1, 2, 42, 3 ); + assertEquals( 42, duration.seconds() ); + assertEquals( 42, duration.get( SECONDS ) ); + } + + @Test + public void shouldExposeNanoseconds() + { + IsoDuration duration = newDuration( 1, 2, 3, 42 ); + assertEquals( 42, duration.nanoseconds() ); + assertEquals( 42, duration.get( NANOS ) ); + } + + @Test + public void shouldFailToGetUnsupportedTemporalUnit() + { + IsoDuration duration = newDuration( 1, 2, 3, 4 ); + + try + { + duration.get( YEARS ); + fail( "Exception expected" ); + } + catch ( UnsupportedTemporalTypeException ignore ) + { + } + } + + @Test + public void shouldExposeSupportedTemporalUnits() + { + IsoDuration duration = newDuration( 1, 2, 3, 4 ); + assertEquals( asList( MONTHS, DAYS, SECONDS, NANOS ), duration.getUnits() ); + } + + @Test + public void shouldAddTo() + { + IsoDuration duration = newDuration( 1, 2, 3, 4 ); + LocalDateTime dateTime = LocalDateTime.of( 1990, 1, 1, 0, 0, 0, 0 ); + + Temporal result = duration.addTo( dateTime ); + + assertEquals( LocalDateTime.of( 1990, 2, 3, 0, 0, 3, 4 ), result ); + } + + @Test + public void shouldSubtractFrom() + { + IsoDuration duration = newDuration( 4, 3, 2, 1 ); + LocalDateTime dateTime = LocalDateTime.of( 1990, 7, 19, 0, 0, 59, 999 ); + + Temporal result = duration.subtractFrom( dateTime ); + + assertEquals( LocalDateTime.of( 1990, 3, 16, 0, 0, 57, 998 ), result ); + } + + @Test + public void shouldImplementEqualsAndHashCode() + { + IsoDuration duration1 = newDuration( 1, 2, 3, 4 ); + IsoDuration duration2 = newDuration( 1, 2, 3, 4 ); + + assertEquals( duration1, duration2 ); + assertEquals( duration1.hashCode(), duration2.hashCode() ); + } + + @Test + public void shouldCreateFromPeriod() + { + Period period = Period.of( 3, 5, 12 ); + + InternalIsoDuration duration = new InternalIsoDuration( period ); + + assertEquals( period.toTotalMonths(), duration.months() ); + assertEquals( period.getDays(), duration.days() ); + assertEquals( 0, duration.seconds() ); + assertEquals( 0, duration.nanoseconds() ); + } + + @Test + public void shouldCreateFromDuration() + { + Duration duration = Duration.ofSeconds( 391784, 4879173 ); + + InternalIsoDuration isoDuration = new InternalIsoDuration( duration ); + + assertEquals( 0, isoDuration.months() ); + assertEquals( 0, isoDuration.days() ); + assertEquals( duration.getSeconds(), isoDuration.seconds() ); + assertEquals( duration.getNano(), isoDuration.nanoseconds() ); + } + + private static IsoDuration newDuration( long months, long days, long seconds, long nanoseconds ) + { + return new InternalIsoDuration( months, days, seconds, nanoseconds ); + } +} diff --git a/driver/src/test/java/org/neo4j/driver/internal/ValuesTest.java b/driver/src/test/java/org/neo4j/driver/internal/ValuesTest.java index de6bc67f0b..4f551bc39e 100644 --- a/driver/src/test/java/org/neo4j/driver/internal/ValuesTest.java +++ b/driver/src/test/java/org/neo4j/driver/internal/ValuesTest.java @@ -23,6 +23,13 @@ import org.junit.Test; import org.junit.rules.ExpectedException; +import java.time.Duration; +import java.time.LocalDate; +import java.time.LocalDateTime; +import java.time.LocalTime; +import java.time.OffsetTime; +import java.time.Period; +import java.time.ZonedDateTime; import java.util.ArrayDeque; import java.util.Collection; import java.util.HashMap; @@ -32,18 +39,29 @@ import java.util.Map; import java.util.Set; +import org.neo4j.driver.internal.value.DateTimeValue; +import org.neo4j.driver.internal.value.DateValue; +import org.neo4j.driver.internal.value.DurationValue; import org.neo4j.driver.internal.value.ListValue; +import org.neo4j.driver.internal.value.LocalDateTimeValue; +import org.neo4j.driver.internal.value.LocalTimeValue; import org.neo4j.driver.internal.value.MapValue; +import org.neo4j.driver.internal.value.TimeValue; import org.neo4j.driver.v1.Value; import org.neo4j.driver.v1.Values; import org.neo4j.driver.v1.exceptions.ClientException; +import org.neo4j.driver.v1.types.IsoDuration; +import org.neo4j.driver.v1.types.Point2D; +import org.neo4j.driver.v1.types.Point3D; import static java.util.Arrays.asList; import static org.hamcrest.CoreMatchers.equalTo; -import static org.hamcrest.MatcherAssert.assertThat; +import static org.hamcrest.Matchers.instanceOf; import static org.hamcrest.collection.IsIterableContainingInOrder.contains; import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertNotEquals; +import static org.junit.Assert.assertThat; +import static org.neo4j.driver.v1.Values.isoDuration; import static org.neo4j.driver.v1.Values.ofDouble; import static org.neo4j.driver.v1.Values.ofFloat; import static org.neo4j.driver.v1.Values.ofInteger; @@ -54,6 +72,8 @@ import static org.neo4j.driver.v1.Values.ofObject; import static org.neo4j.driver.v1.Values.ofString; import static org.neo4j.driver.v1.Values.ofToString; +import static org.neo4j.driver.v1.Values.point2D; +import static org.neo4j.driver.v1.Values.point3D; import static org.neo4j.driver.v1.Values.value; import static org.neo4j.driver.v1.Values.values; @@ -287,4 +307,182 @@ public void shouldHandleIterator() throws Throwable // When/Then assertThat( val.asList(), Matchers.containsInAnyOrder( "hello", "world" )); } + + @Test + public void shouldCreateDateValueFromLocalDate() + { + LocalDate localDate = LocalDate.now(); + Value value = value( localDate ); + + assertThat( value, instanceOf( DateValue.class ) ); + assertEquals( localDate, value.asLocalDate() ); + } + + @Test + public void shouldCreateDateValue() + { + Object localDate = LocalDate.now(); + Value value = value( localDate ); + + assertThat( value, instanceOf( DateValue.class ) ); + assertEquals( localDate, value.asObject() ); + } + + @Test + public void shouldCreateTimeValueFromOffsetTime() + { + OffsetTime offsetTime = OffsetTime.now(); + Value value = value( offsetTime ); + + assertThat( value, instanceOf( TimeValue.class ) ); + assertEquals( offsetTime, value.asOffsetTime() ); + } + + @Test + public void shouldCreateTimeValue() + { + OffsetTime offsetTime = OffsetTime.now(); + Value value = value( offsetTime ); + + assertThat( value, instanceOf( TimeValue.class ) ); + assertEquals( offsetTime, value.asObject() ); + } + + @Test + public void shouldCreateLocalTimeValueFromLocalTime() + { + LocalTime localTime = LocalTime.now(); + Value value = value( localTime ); + + assertThat( value, instanceOf( LocalTimeValue.class ) ); + assertEquals( localTime, value.asLocalTime() ); + } + + @Test + public void shouldCreateLocalTimeValue() + { + LocalTime localTime = LocalTime.now(); + Value value = value( localTime ); + + assertThat( value, instanceOf( LocalTimeValue.class ) ); + assertEquals( localTime, value.asObject() ); + } + + @Test + public void shouldCreateLocalDateTimeValueFromLocalDateTime() + { + LocalDateTime localDateTime = LocalDateTime.now(); + Value value = value( localDateTime ); + + assertThat( value, instanceOf( LocalDateTimeValue.class ) ); + assertEquals( localDateTime, value.asLocalDateTime() ); + } + + @Test + public void shouldCreateLocalDateTimeValue() + { + LocalDateTime localDateTime = LocalDateTime.now(); + Value value = value( localDateTime ); + + assertThat( value, instanceOf( LocalDateTimeValue.class ) ); + assertEquals( localDateTime, value.asObject() ); + } + + @Test + public void shouldCreateDateTimeValueFromZonedDateTime() + { + ZonedDateTime zonedDateTime = ZonedDateTime.now(); + Value value = value( zonedDateTime ); + + assertThat( value, instanceOf( DateTimeValue.class ) ); + assertEquals( zonedDateTime, value.asZonedDateTime() ); + } + + @Test + public void shouldCreateDateTimeValue() + { + ZonedDateTime zonedDateTime = ZonedDateTime.now(); + Value value = value( zonedDateTime ); + + assertThat( value, instanceOf( DateTimeValue.class ) ); + assertEquals( zonedDateTime, value.asObject() ); + } + + @Test + public void shouldCreateIsoDurationValue() + { + Value value = isoDuration( 42_1, 42_2, 42_3, 42_4 ); + + assertThat( value, instanceOf( DurationValue.class ) ); + IsoDuration duration = value.asIsoDuration(); + + assertEquals( 42_1, duration.months() ); + assertEquals( 42_2, duration.days() ); + assertEquals( 42_3, duration.seconds() ); + assertEquals( 42_4, duration.nanoseconds() ); + } + + @Test + public void shouldCreateValueFromIsoDuration() + { + Value durationValue1 = isoDuration( 1, 2, 3, 4 ); + IsoDuration duration = durationValue1.asIsoDuration(); + Value durationValue2 = value( duration ); + + assertEquals( duration, durationValue1.asIsoDuration() ); + assertEquals( duration, durationValue2.asIsoDuration() ); + assertEquals( durationValue1, durationValue2 ); + } + + @Test + public void shouldCreateValueFromPeriod() + { + Period period = Period.of( 5, 11, 190 ); + + Value value = value( period ); + IsoDuration isoDuration = value.asIsoDuration(); + + assertEquals( period.toTotalMonths(), isoDuration.months() ); + assertEquals( period.getDays(), isoDuration.days() ); + assertEquals( 0, isoDuration.seconds() ); + assertEquals( 0, isoDuration.nanoseconds() ); + } + + @Test + public void shouldCreateValueFromDuration() + { + Duration duration = Duration.ofSeconds( 183951, 4384718937L ); + + Value value = value( duration ); + IsoDuration isoDuration = value.asIsoDuration(); + + assertEquals( 0, isoDuration.months() ); + assertEquals( 0, isoDuration.days() ); + assertEquals( duration.getSeconds(), isoDuration.seconds() ); + assertEquals( duration.getNano(), isoDuration.nanoseconds() ); + } + + @Test + public void shouldCreateValueFromPoint2D() + { + Value point2DValue1 = point2D( 1, 2, 3 ); + Point2D point2D = point2DValue1.asPoint2D(); + Value point2DValue2 = value( point2D ); + + assertEquals( point2D, point2DValue1.asPoint2D() ); + assertEquals( point2D, point2DValue2.asPoint2D() ); + assertEquals( point2DValue1, point2DValue2 ); + } + + @Test + public void shouldCreateValueFromPoint3D() + { + Value point3DValue1 = point3D( 1, 2, 3, 4 ); + Point3D point3D = point3DValue1.asPoint3D(); + Value point3DValue2 = value( point3D ); + + assertEquals( point3D, point3DValue1.asPoint3D() ); + assertEquals( point3D, point3DValue2.asPoint3D() ); + assertEquals( point3DValue1, point3DValue2 ); + } } diff --git a/driver/src/test/java/org/neo4j/driver/internal/messaging/PackStreamMessageFormatV2Test.java b/driver/src/test/java/org/neo4j/driver/internal/messaging/PackStreamMessageFormatV2Test.java index 80e4d9b6bf..f655126172 100644 --- a/driver/src/test/java/org/neo4j/driver/internal/messaging/PackStreamMessageFormatV2Test.java +++ b/driver/src/test/java/org/neo4j/driver/internal/messaging/PackStreamMessageFormatV2Test.java @@ -23,6 +23,13 @@ import org.junit.Test; import java.io.IOException; +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.ArrayList; import java.util.Collections; import java.util.List; @@ -34,7 +41,14 @@ import org.neo4j.driver.internal.util.ByteBufOutput; import org.neo4j.driver.internal.util.ThrowingConsumer; import org.neo4j.driver.v1.Value; +import org.neo4j.driver.v1.Values; +import org.neo4j.driver.v1.types.IsoDuration; +import static java.time.Month.APRIL; +import static java.time.Month.AUGUST; +import static java.time.Month.DECEMBER; +import static java.time.ZoneOffset.UTC; +import static java.util.Arrays.asList; import static java.util.Collections.singletonMap; import static org.junit.Assert.assertEquals; import static org.junit.Assert.fail; @@ -43,9 +57,14 @@ import static org.mockito.Mockito.mock; import static org.neo4j.driver.internal.messaging.PackStreamMessageFormatV1.MSG_RECORD; import static org.neo4j.driver.internal.packstream.PackStream.FLOAT_64; +import static org.neo4j.driver.internal.packstream.PackStream.INT_16; +import static org.neo4j.driver.internal.packstream.PackStream.INT_32; +import static org.neo4j.driver.internal.packstream.PackStream.INT_64; import static org.neo4j.driver.internal.packstream.PackStream.Packer; +import static org.neo4j.driver.internal.packstream.PackStream.STRING_8; import static org.neo4j.driver.v1.Values.point2D; import static org.neo4j.driver.v1.Values.point3D; +import static org.neo4j.driver.v1.Values.value; import static org.neo4j.driver.v1.util.TestUtil.assertByteBufContains; public class PackStreamMessageFormatV2Test @@ -71,7 +90,7 @@ public void shouldFailToCreateWriterWithoutByteArraySupport() public void shouldWritePoint2D() throws Exception { ByteBuf buf = Unpooled.buffer(); - MessageFormat.Writer writer = messageFormat.newWriter( new ByteBufOutput( buf ), true ); + MessageFormat.Writer writer = newWriter( buf ); writer.write( new RunMessage( "RETURN $point", singletonMap( "point", point2D( 42, 12.99, -180.0 ) ) ) ); @@ -86,7 +105,7 @@ public void shouldWritePoint2D() throws Exception public void shouldWritePoint3D() throws Exception { ByteBuf buf = Unpooled.buffer(); - MessageFormat.Writer writer = messageFormat.newWriter( new ByteBufOutput( buf ), true ); + MessageFormat.Writer writer = newWriter( buf ); writer.write( new RunMessage( "RETURN $point", singletonMap( "point", point3D( 42, 0.51, 2.99, 100.123 ) ) ) ); @@ -102,13 +121,15 @@ public void shouldReadPoint2D() throws Exception { InternalPoint2D point = new InternalPoint2D( 42, 120.65, -99.2 ); - testReadingOfPoint( packer -> + Object unpacked = packAndUnpackValue( packer -> { packer.packStructHeader( 3, (byte) 'X' ); packer.pack( point.srid() ); packer.pack( point.x() ); packer.pack( point.y() ); - }, point ); + } ); + + assertEquals( point, unpacked ); } @Test @@ -116,17 +137,251 @@ public void shouldReadPoint3D() throws Exception { InternalPoint3D point = new InternalPoint3D( 42, 85.391, 98.8, 11.1 ); - testReadingOfPoint( packer -> + Object unpacked = packAndUnpackValue( packer -> { packer.packStructHeader( 4, (byte) 'Y' ); packer.pack( point.srid() ); packer.pack( point.x() ); packer.pack( point.y() ); packer.pack( point.z() ); - }, point ); + } ); + + assertEquals( point, unpacked ); + } + + @Test + public void shouldWriteDate() throws Exception + { + LocalDate date = LocalDate.ofEpochDay( 2147483650L ); + ByteBuf buf = Unpooled.buffer(); + MessageFormat.Writer writer = newWriter( buf ); + + writer.write( new RunMessage( "RETURN $date", singletonMap( "date", value( date ) ) ) ); + + int index = buf.readableBytes() - Long.BYTES - Byte.BYTES; + ByteBuf tailSlice = buf.slice( index, buf.readableBytes() - index ); + + assertByteBufContains( tailSlice, INT_64, date.toEpochDay() ); + } + + @Test + public void shouldReadDate() throws Exception + { + LocalDate date = LocalDate.of( 2012, AUGUST, 3 ); + + Object unpacked = packAndUnpackValue( packer -> + { + packer.packStructHeader( 1, (byte) 'D' ); + packer.pack( date.toEpochDay() ); + } ); + + assertEquals( date, unpacked ); + } + + @Test + public void shouldWriteTime() throws Exception + { + OffsetTime time = OffsetTime.of( 4, 16, 20, 999, ZoneOffset.MIN ); + ByteBuf buf = Unpooled.buffer(); + MessageFormat.Writer writer = newWriter( buf ); + + writer.write( new RunMessage( "RETURN $time", singletonMap( "time", value( time ) ) ) ); + + int index = buf.readableBytes() - Long.BYTES - Byte.BYTES - Integer.BYTES - Byte.BYTES; + ByteBuf tailSlice = buf.slice( index, buf.readableBytes() - index ); + + assertByteBufContains( tailSlice, INT_64, time.withOffsetSameInstant( UTC ).toLocalTime().toNanoOfDay(), INT_32, time.getOffset().getTotalSeconds() ); + } + + @Test + public void shouldReadTime() throws Exception + { + OffsetTime time = OffsetTime.of( 23, 59, 59, 999, ZoneOffset.MAX ); + + Object unpacked = packAndUnpackValue( packer -> + { + packer.packStructHeader( 2, (byte) 'T' ); + packer.pack( time.withOffsetSameInstant( UTC ).toLocalTime().toNanoOfDay() ); + packer.pack( time.getOffset().getTotalSeconds() ); + } ); + + assertEquals( time, unpacked ); } - private void testReadingOfPoint( ThrowingConsumer packAction, Object expected ) throws Exception + @Test + public void shouldWriteLocalTime() throws Exception + { + LocalTime time = LocalTime.of( 12, 9, 18, 999_888 ); + ByteBuf buf = Unpooled.buffer(); + MessageFormat.Writer writer = newWriter( buf ); + + writer.write( new RunMessage( "RETURN $time", singletonMap( "time", value( time ) ) ) ); + + int index = buf.readableBytes() - Long.BYTES - Byte.BYTES; + ByteBuf tailSlice = buf.slice( index, buf.readableBytes() - index ); + + assertByteBufContains( tailSlice, INT_64, time.toNanoOfDay() ); + } + + @Test + public void shouldReadLocalTime() throws Exception + { + LocalTime time = LocalTime.of( 12, 25 ); + + Object unpacked = packAndUnpackValue( packer -> + { + packer.packStructHeader( 1, (byte) 't' ); + packer.pack( time.toNanoOfDay() ); + } ); + + assertEquals( time, unpacked ); + } + + @Test + public void shouldWriteLocalDateTime() throws Exception + { + LocalDateTime dateTime = LocalDateTime.of( 2049, DECEMBER, 12, 17, 25, 49, 199 ); + ByteBuf buf = Unpooled.buffer(); + MessageFormat.Writer writer = newWriter( buf ); + + writer.write( new RunMessage( "RETURN $dateTime", singletonMap( "dateTime", value( dateTime ) ) ) ); + + int index = buf.readableBytes() - Long.BYTES - Byte.BYTES - Short.BYTES - Byte.BYTES; + ByteBuf tailSlice = buf.slice( index, buf.readableBytes() - index ); + + assertByteBufContains( tailSlice, INT_64, dateTime.toEpochSecond( UTC ), INT_16, (short) dateTime.getNano() ); + } + + @Test + public void shouldReadLocalDateTime() throws Exception + { + LocalDateTime dateTime = LocalDateTime.of( 1999, APRIL, 3, 19, 5, 5, 100_200_300 ); + + Object unpacked = packAndUnpackValue( packer -> + { + packer.packStructHeader( 2, (byte) 'd' ); + packer.pack( dateTime.toEpochSecond( UTC ) ); + packer.pack( dateTime.getNano() ); + } ); + + assertEquals( dateTime, unpacked ); + } + + @Test + public void shouldWriteZonedDateTimeWithOffset() throws Exception + { + ZoneOffset zoneOffset = ZoneOffset.ofHoursMinutes( 9, 30 ); + ZonedDateTime dateTime = ZonedDateTime.of( 2000, 1, 10, 12, 2, 49, 300, zoneOffset ); + ByteBuf buf = Unpooled.buffer(); + MessageFormat.Writer writer = newWriter( buf ); + + writer.write( new RunMessage( "RETURN $dateTime", singletonMap( "dateTime", value( dateTime ) ) ) ); + + int index = buf.readableBytes() - Integer.BYTES - Byte.BYTES - Short.BYTES - Byte.BYTES - Integer.BYTES - Byte.BYTES; + ByteBuf tailSlice = buf.slice( index, buf.readableBytes() - index ); + + assertByteBufContains( tailSlice, INT_32, (int) dateTime.toEpochSecond(), INT_16, (short) dateTime.getNano(), INT_32, zoneOffset.getTotalSeconds() ); + } + + @Test + public void shouldReadZonedDateTimeWithOffset() throws Exception + { + ZoneOffset zoneOffset = ZoneOffset.ofHoursMinutes( -7, -15 ); + ZonedDateTime dateTime = ZonedDateTime.of( 1823, 1, 12, 23, 59, 59, 999_999_999, zoneOffset ); + + Object unpacked = packAndUnpackValue( packer -> + { + packer.packStructHeader( 3, (byte) 'F' ); + packer.pack( dateTime.toInstant().getEpochSecond() ); + packer.pack( dateTime.toInstant().getNano() ); + packer.pack( zoneOffset.getTotalSeconds() ); + } ); + + assertEquals( dateTime, unpacked ); + } + + @Test + public void shouldWriteZonedDateTimeWithZoneId() throws Exception + { + String zoneName = "Europe/Stockholm"; + byte[] zoneNameBytes = zoneName.getBytes(); + ZonedDateTime dateTime = ZonedDateTime.of( 2000, 1, 10, 12, 2, 49, 300, ZoneId.of( zoneName ) ); + + ByteBuf buf = Unpooled.buffer(); + MessageFormat.Writer writer = newWriter( buf ); + + writer.write( new RunMessage( "RETURN $dateTime", singletonMap( "dateTime", value( dateTime ) ) ) ); + + int index = buf.readableBytes() - zoneNameBytes.length - Byte.BYTES - Byte.BYTES - Short.BYTES - Byte.BYTES - Integer.BYTES - Byte.BYTES; + ByteBuf tailSlice = buf.slice( index, buf.readableBytes() - index ); + + List expectedBuf = new ArrayList<>( asList( + INT_32, (int) dateTime.toInstant().getEpochSecond(), + INT_16, (short) dateTime.toInstant().getNano(), + STRING_8, (byte) zoneNameBytes.length ) ); + + for ( byte b : zoneNameBytes ) + { + expectedBuf.add( b ); + } + + assertByteBufContains( tailSlice, expectedBuf.toArray( new Number[0] ) ); + } + + @Test + public void shouldReadZonedDateTimeWithZoneId() throws Exception + { + String zoneName = "Europe/Stockholm"; + ZonedDateTime dateTime = ZonedDateTime.of( 1823, 1, 12, 23, 59, 59, 999_999_999, ZoneId.of( zoneName ) ); + + Object unpacked = packAndUnpackValue( packer -> + { + packer.packStructHeader( 3, (byte) 'f' ); + packer.pack( dateTime.toInstant().getEpochSecond() ); + packer.pack( dateTime.toInstant().getNano() ); + packer.pack( zoneName ); + } ); + + assertEquals( dateTime, unpacked ); + } + + @Test + public void shouldWriteDuration() throws Exception + { + Value durationValue = Values.isoDuration( Long.MAX_VALUE - 1, Integer.MAX_VALUE - 1, Short.MAX_VALUE - 1, Byte.MAX_VALUE - 1 ); + IsoDuration duration = durationValue.asIsoDuration(); + + ByteBuf buf = Unpooled.buffer(); + MessageFormat.Writer writer = newWriter( buf ); + + writer.write( new RunMessage( "RETURN $duration", singletonMap( "duration", durationValue ) ) ); + + int index = buf.readableBytes() - Long.BYTES - Byte.BYTES - Integer.BYTES - Byte.BYTES - Short.BYTES - Byte.BYTES - Byte.BYTES; + ByteBuf tailSlice = buf.slice( index, buf.readableBytes() - index ); + + assertByteBufContains( tailSlice, + INT_64, duration.months(), INT_32, (int) duration.days(), INT_16, (short) duration.seconds(), (byte) duration.nanoseconds() ); + } + + @Test + public void shouldReadDuration() throws Exception + { + Value durationValue = Values.isoDuration( 17, 22, 99, 15 ); + IsoDuration duration = durationValue.asIsoDuration(); + + Object unpacked = packAndUnpackValue( packer -> + { + packer.packStructHeader( 4, (byte) 'E' ); + packer.pack( duration.months() ); + packer.pack( duration.days() ); + packer.pack( duration.seconds() ); + packer.pack( duration.nanoseconds() ); + } ); + + assertEquals( duration, unpacked ); + } + + private Object packAndUnpackValue( ThrowingConsumer packAction ) throws Exception { ByteBuf buf = Unpooled.buffer(); try @@ -146,7 +401,7 @@ private void testReadingOfPoint( ThrowingConsumer packAction, Object exp input.stop(); assertEquals( 1, values.size() ); - assertEquals( expected, values.get( 0 ).asObject() ); + return values.get( 0 ).asObject(); } finally { @@ -165,4 +420,9 @@ private static MessageHandler recordMemorizingHandler( List values ) thro } ).when( messageHandler ).handleRecordMessage( any() ); return messageHandler; } + + private MessageFormat.Writer newWriter( ByteBuf buf ) + { + return messageFormat.newWriter( new ByteBufOutput( buf ), true ); + } } diff --git a/driver/src/test/java/org/neo4j/driver/internal/value/BooleanValueTest.java b/driver/src/test/java/org/neo4j/driver/internal/value/BooleanValueTest.java index a0739d3b40..11a74d8a55 100644 --- a/driver/src/test/java/org/neo4j/driver/internal/value/BooleanValueTest.java +++ b/driver/src/test/java/org/neo4j/driver/internal/value/BooleanValueTest.java @@ -29,7 +29,6 @@ import static org.junit.Assert.assertFalse; import static org.junit.Assert.assertThat; import static org.junit.Assert.assertTrue; - import static org.neo4j.driver.internal.value.BooleanValue.FALSE; import static org.neo4j.driver.internal.value.BooleanValue.TRUE; @@ -102,8 +101,8 @@ public void shouldNotBeNull() @Test public void shouldTypeAsBoolean() { - assertThat( TRUE.typeConstructor(), equalTo( TypeConstructor.BOOLEAN_TyCon ) ); - assertThat( BooleanValue.FALSE.typeConstructor(), equalTo( TypeConstructor.BOOLEAN_TyCon ) ); + assertThat( TRUE.typeConstructor(), equalTo( TypeConstructor.BOOLEAN ) ); + assertThat( BooleanValue.FALSE.typeConstructor(), equalTo( TypeConstructor.BOOLEAN ) ); } @Test diff --git a/driver/src/test/java/org/neo4j/driver/internal/value/BytesValueTest.java b/driver/src/test/java/org/neo4j/driver/internal/value/BytesValueTest.java index 898fcba6ea..0d4d441064 100644 --- a/driver/src/test/java/org/neo4j/driver/internal/value/BytesValueTest.java +++ b/driver/src/test/java/org/neo4j/driver/internal/value/BytesValueTest.java @@ -19,6 +19,7 @@ package org.neo4j.driver.internal.value; import org.junit.Test; + import org.neo4j.driver.internal.types.InternalTypeSystem; import org.neo4j.driver.internal.types.TypeConstructor; import org.neo4j.driver.v1.Value; @@ -87,7 +88,7 @@ public void shouldNotBeNull() public void shouldTypeAsString() { InternalValue value = new BytesValue( TEST_BYTES ); - assertThat( value.typeConstructor(), equalTo( TypeConstructor.BYTES_TyCon ) ); + assertThat( value.typeConstructor(), equalTo( TypeConstructor.BYTES ) ); } @Test diff --git a/driver/src/test/java/org/neo4j/driver/internal/value/DateTimeValueTest.java b/driver/src/test/java/org/neo4j/driver/internal/value/DateTimeValueTest.java new file mode 100644 index 0000000000..c5db59c4a9 --- /dev/null +++ b/driver/src/test/java/org/neo4j/driver/internal/value/DateTimeValueTest.java @@ -0,0 +1,74 @@ +/* + * Copyright (c) 2002-2018 "Neo Technology," + * Network Engine for Objects in Lund AB [http://neotechnology.com] + * + * This file is part of Neo4j. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.neo4j.driver.internal.value; + +import org.junit.Test; + +import java.time.ZoneId; +import java.time.ZoneOffset; +import java.time.ZonedDateTime; + +import org.neo4j.driver.internal.types.InternalTypeSystem; +import org.neo4j.driver.v1.exceptions.value.Uncoercible; + +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.fail; + +public class DateTimeValueTest +{ + @Test + public void shouldHaveCorrectType() + { + ZonedDateTime dateTime = ZonedDateTime.of( 1991, 2, 24, 12, 0, 0, 999_000, ZoneOffset.ofHours( -5 ) ); + DateTimeValue dateTimeValue = new DateTimeValue( dateTime ); + assertEquals( InternalTypeSystem.TYPE_SYSTEM.DATE_TIME(), dateTimeValue.type() ); + } + + @Test + public void shouldSupportAsObject() + { + ZonedDateTime dateTime = ZonedDateTime.of( 2015, 8, 2, 23, 59, 59, 999_999, ZoneId.of( "Europe/Stockholm" ) ); + DateTimeValue dateTimeValue = new DateTimeValue( dateTime ); + assertEquals( dateTime, dateTimeValue.asObject() ); + } + + @Test + public void shouldSupportAsZonedDateTime() + { + ZonedDateTime dateTime = ZonedDateTime.of( 1822, 9, 24, 9, 23, 57, 123, ZoneOffset.ofHoursMinutes( 12, 15 ) ); + DateTimeValue dateTimeValue = new DateTimeValue( dateTime ); + assertEquals( dateTime, dateTimeValue.asZonedDateTime() ); + } + + @Test + public void shouldNotSupportAsLong() + { + ZonedDateTime dateTime = ZonedDateTime.now(); + DateTimeValue dateTimeValue = new DateTimeValue( dateTime ); + + try + { + dateTimeValue.asLong(); + fail( "Exception expected" ); + } + catch ( Uncoercible ignore ) + { + } + } +} diff --git a/driver/src/test/java/org/neo4j/driver/internal/value/DateValueTest.java b/driver/src/test/java/org/neo4j/driver/internal/value/DateValueTest.java new file mode 100644 index 0000000000..a078e3d083 --- /dev/null +++ b/driver/src/test/java/org/neo4j/driver/internal/value/DateValueTest.java @@ -0,0 +1,72 @@ +/* + * Copyright (c) 2002-2018 "Neo Technology," + * Network Engine for Objects in Lund AB [http://neotechnology.com] + * + * This file is part of Neo4j. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.neo4j.driver.internal.value; + +import org.junit.Test; + +import java.time.LocalDate; + +import org.neo4j.driver.internal.types.InternalTypeSystem; +import org.neo4j.driver.v1.exceptions.value.Uncoercible; + +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.fail; + +public class DateValueTest +{ + @Test + public void shouldHaveCorrectType() + { + LocalDate localDate = LocalDate.now(); + DateValue dateValue = new DateValue( localDate ); + assertEquals( InternalTypeSystem.TYPE_SYSTEM.DATE(), dateValue.type() ); + } + + @Test + public void shouldSupportAsObject() + { + LocalDate localDate = LocalDate.now(); + DateValue dateValue = new DateValue( localDate ); + assertEquals( localDate, dateValue.asObject() ); + } + + @Test + public void shouldSupportAsLocalDate() + { + LocalDate localDate = LocalDate.now(); + DateValue dateValue = new DateValue( localDate ); + assertEquals( localDate, dateValue.asLocalDate() ); + } + + @Test + public void shouldNotSupportAsLong() + { + LocalDate localDate = LocalDate.now(); + DateValue dateValue = new DateValue( localDate ); + + try + { + dateValue.asLong(); + fail( "Exception expected" ); + } + catch ( Uncoercible ignore ) + { + } + } +} diff --git a/driver/src/test/java/org/neo4j/driver/internal/value/DurationValueTest.java b/driver/src/test/java/org/neo4j/driver/internal/value/DurationValueTest.java new file mode 100644 index 0000000000..4e812be596 --- /dev/null +++ b/driver/src/test/java/org/neo4j/driver/internal/value/DurationValueTest.java @@ -0,0 +1,77 @@ +/* + * Copyright (c) 2002-2018 "Neo Technology," + * Network Engine for Objects in Lund AB [http://neotechnology.com] + * + * This file is part of Neo4j. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.neo4j.driver.internal.value; + +import org.junit.Test; + +import org.neo4j.driver.internal.InternalIsoDuration; +import org.neo4j.driver.internal.types.InternalTypeSystem; +import org.neo4j.driver.v1.exceptions.value.Uncoercible; +import org.neo4j.driver.v1.types.IsoDuration; + +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.fail; + +public class DurationValueTest +{ + @Test + public void shouldHaveCorrectType() + { + IsoDuration duration = newDuration( 1, 2, 3, 4 ); + DurationValue durationValue = new DurationValue( duration ); + assertEquals( InternalTypeSystem.TYPE_SYSTEM.DURATION(), durationValue.type() ); + } + + @Test + public void shouldSupportAsObject() + { + IsoDuration duration = newDuration( 11, 22, 33, 44 ); + DurationValue durationValue = new DurationValue( duration ); + assertEquals( duration, durationValue.asObject() ); + } + + @Test + public void shouldSupportAsOffsetTime() + { + IsoDuration duration = newDuration( 111, 222, 333, 444 ); + DurationValue durationValue = new DurationValue( duration ); + assertEquals( duration, durationValue.asIsoDuration() ); + } + + @Test + public void shouldNotSupportAsLong() + { + IsoDuration duration = newDuration( 1111, 2222, 3333, 4444 ); + DurationValue durationValue = new DurationValue( duration ); + + try + { + durationValue.asLong(); + fail( "Exception expected" ); + } + catch ( Uncoercible ignore ) + { + } + } + + private static IsoDuration newDuration( long months, long days, long seconds, long nanoseconds ) + { + return new InternalIsoDuration( months, days, seconds, nanoseconds ); + } +} diff --git a/driver/src/test/java/org/neo4j/driver/internal/value/FloatValueTest.java b/driver/src/test/java/org/neo4j/driver/internal/value/FloatValueTest.java index 55a66839a5..46f520972d 100644 --- a/driver/src/test/java/org/neo4j/driver/internal/value/FloatValueTest.java +++ b/driver/src/test/java/org/neo4j/driver/internal/value/FloatValueTest.java @@ -24,9 +24,9 @@ import org.neo4j.driver.internal.types.InternalTypeSystem; import org.neo4j.driver.internal.types.TypeConstructor; -import org.neo4j.driver.v1.types.TypeSystem; import org.neo4j.driver.v1.Value; import org.neo4j.driver.v1.exceptions.value.LossyCoercion; +import org.neo4j.driver.v1.types.TypeSystem; import static org.hamcrest.CoreMatchers.equalTo; import static org.hamcrest.CoreMatchers.notNullValue; @@ -105,7 +105,7 @@ public void shouldNotBeNull() public void shouldTypeAsFloat() { InternalValue value = new FloatValue( 6.28 ); - assertThat( value.typeConstructor(), equalTo( TypeConstructor.FLOAT_TyCon ) ); + assertThat( value.typeConstructor(), equalTo( TypeConstructor.FLOAT ) ); } @Test diff --git a/driver/src/test/java/org/neo4j/driver/internal/value/IntegerValueTest.java b/driver/src/test/java/org/neo4j/driver/internal/value/IntegerValueTest.java index 7f34e03df6..3b96d93603 100644 --- a/driver/src/test/java/org/neo4j/driver/internal/value/IntegerValueTest.java +++ b/driver/src/test/java/org/neo4j/driver/internal/value/IntegerValueTest.java @@ -24,9 +24,9 @@ import org.neo4j.driver.internal.types.InternalTypeSystem; import org.neo4j.driver.internal.types.TypeConstructor; -import org.neo4j.driver.v1.types.TypeSystem; import org.neo4j.driver.v1.Value; import org.neo4j.driver.v1.exceptions.value.LossyCoercion; +import org.neo4j.driver.v1.types.TypeSystem; import static org.hamcrest.CoreMatchers.equalTo; import static org.hamcrest.CoreMatchers.notNullValue; @@ -110,7 +110,7 @@ public void shouldNotBeNull() public void shouldTypeAsInteger() { InternalValue value = new IntegerValue( 1L ); - assertThat( value.typeConstructor(), equalTo( TypeConstructor.INTEGER_TyCon ) ); + assertThat( value.typeConstructor(), equalTo( TypeConstructor.INTEGER ) ); } @Test diff --git a/driver/src/test/java/org/neo4j/driver/internal/value/LocalDateTimeValueTest.java b/driver/src/test/java/org/neo4j/driver/internal/value/LocalDateTimeValueTest.java new file mode 100644 index 0000000000..7dcc4d82b9 --- /dev/null +++ b/driver/src/test/java/org/neo4j/driver/internal/value/LocalDateTimeValueTest.java @@ -0,0 +1,75 @@ +/* + * Copyright (c) 2002-2018 "Neo Technology," + * Network Engine for Objects in Lund AB [http://neotechnology.com] + * + * This file is part of Neo4j. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.neo4j.driver.internal.value; + +import org.junit.Test; + +import java.time.LocalDateTime; + +import org.neo4j.driver.internal.types.InternalTypeSystem; +import org.neo4j.driver.v1.exceptions.value.Uncoercible; + +import static java.time.Month.AUGUST; +import static java.time.Month.FEBRUARY; +import static java.time.Month.JANUARY; +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.fail; + +public class LocalDateTimeValueTest +{ + @Test + public void shouldHaveCorrectType() + { + LocalDateTime dateTime = LocalDateTime.of( 1991, AUGUST, 24, 12, 0, 0 ); + LocalDateTimeValue dateTimeValue = new LocalDateTimeValue( dateTime ); + assertEquals( InternalTypeSystem.TYPE_SYSTEM.LOCAL_DATE_TIME(), dateTimeValue.type() ); + } + + @Test + public void shouldSupportAsObject() + { + LocalDateTime dateTime = LocalDateTime.of( 2015, FEBRUARY, 2, 23, 59, 59, 999_999 ); + LocalDateTimeValue dateTimeValue = new LocalDateTimeValue( dateTime ); + assertEquals( dateTime, dateTimeValue.asObject() ); + } + + @Test + public void shouldSupportAsLocalDateTime() + { + LocalDateTime dateTime = LocalDateTime.of( 1822, JANUARY, 24, 9, 23, 57, 123 ); + LocalDateTimeValue dateTimeValue = new LocalDateTimeValue( dateTime ); + assertEquals( dateTime, dateTimeValue.asLocalDateTime() ); + } + + @Test + public void shouldNotSupportAsLong() + { + LocalDateTime dateTime = LocalDateTime.now(); + LocalDateTimeValue dateTimeValue = new LocalDateTimeValue( dateTime ); + + try + { + dateTimeValue.asLong(); + fail( "Exception expected" ); + } + catch ( Uncoercible ignore ) + { + } + } +} diff --git a/driver/src/test/java/org/neo4j/driver/internal/value/LocalTimeValueTest.java b/driver/src/test/java/org/neo4j/driver/internal/value/LocalTimeValueTest.java new file mode 100644 index 0000000000..433a6287c5 --- /dev/null +++ b/driver/src/test/java/org/neo4j/driver/internal/value/LocalTimeValueTest.java @@ -0,0 +1,72 @@ +/* + * Copyright (c) 2002-2018 "Neo Technology," + * Network Engine for Objects in Lund AB [http://neotechnology.com] + * + * This file is part of Neo4j. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.neo4j.driver.internal.value; + +import org.junit.Test; + +import java.time.LocalTime; + +import org.neo4j.driver.internal.types.InternalTypeSystem; +import org.neo4j.driver.v1.exceptions.value.Uncoercible; + +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.fail; + +public class LocalTimeValueTest +{ + @Test + public void shouldHaveCorrectType() + { + LocalTime time = LocalTime.of( 23, 59, 59 ); + LocalTimeValue timeValue = new LocalTimeValue( time ); + assertEquals( InternalTypeSystem.TYPE_SYSTEM.LOCAL_TIME(), timeValue.type() ); + } + + @Test + public void shouldSupportAsObject() + { + LocalTime time = LocalTime.of( 1, 17, 59, 999 ); + LocalTimeValue timeValue = new LocalTimeValue( time ); + assertEquals( time, timeValue.asObject() ); + } + + @Test + public void shouldSupportAsLocalTime() + { + LocalTime time = LocalTime.of( 12, 59, 12, 999_999_999 ); + LocalTimeValue timeValue = new LocalTimeValue( time ); + assertEquals( time, timeValue.asLocalTime() ); + } + + @Test + public void shouldNotSupportAsLong() + { + LocalTime time = LocalTime.now(); + LocalTimeValue timeValue = new LocalTimeValue( time ); + + try + { + timeValue.asLong(); + fail( "Exception expected" ); + } + catch ( Uncoercible ignore ) + { + } + } +} diff --git a/driver/src/test/java/org/neo4j/driver/internal/value/NodeValueTest.java b/driver/src/test/java/org/neo4j/driver/internal/value/NodeValueTest.java index 42a3750825..f01c252267 100644 --- a/driver/src/test/java/org/neo4j/driver/internal/value/NodeValueTest.java +++ b/driver/src/test/java/org/neo4j/driver/internal/value/NodeValueTest.java @@ -67,7 +67,7 @@ public void shouldHaveCorrectType() throws Throwable public void shouldTypeAsNode() { InternalValue value = emptyNodeValue(); - assertThat( value.typeConstructor(), equalTo( TypeConstructor.NODE_TyCon ) ); + assertThat( value.typeConstructor(), equalTo( TypeConstructor.NODE ) ); } private NodeValue emptyNodeValue() diff --git a/driver/src/test/java/org/neo4j/driver/internal/value/NullValueTest.java b/driver/src/test/java/org/neo4j/driver/internal/value/NullValueTest.java index 5c56bb4046..c2db722511 100644 --- a/driver/src/test/java/org/neo4j/driver/internal/value/NullValueTest.java +++ b/driver/src/test/java/org/neo4j/driver/internal/value/NullValueTest.java @@ -43,6 +43,6 @@ public void shouldBeNull() @Test public void shouldTypeAsNull() { - assertThat( ( (InternalValue) NullValue.NULL ).typeConstructor(), equalTo( TypeConstructor.NULL_TyCon ) ); + assertThat( ((InternalValue) NullValue.NULL).typeConstructor(), equalTo( TypeConstructor.NULL ) ); } } diff --git a/driver/src/test/java/org/neo4j/driver/internal/value/RelationshipValueTest.java b/driver/src/test/java/org/neo4j/driver/internal/value/RelationshipValueTest.java index 0215d208c7..724a244887 100644 --- a/driver/src/test/java/org/neo4j/driver/internal/value/RelationshipValueTest.java +++ b/driver/src/test/java/org/neo4j/driver/internal/value/RelationshipValueTest.java @@ -25,12 +25,10 @@ import org.neo4j.driver.v1.Value; import static java.util.Collections.singletonMap; - import static org.hamcrest.CoreMatchers.equalTo; import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertFalse; import static org.junit.Assert.assertThat; - import static org.neo4j.driver.v1.Values.value; public class RelationshipValueTest @@ -60,7 +58,7 @@ public void shouldNotBeNull() public void shouldTypeAsRelationship() { InternalValue value = emptyRelationshipValue(); - assertThat( value.typeConstructor(), equalTo( TypeConstructor.RELATIONSHIP_TyCon ) ); + assertThat( value.typeConstructor(), equalTo( TypeConstructor.RELATIONSHIP ) ); } private RelationshipValue emptyRelationshipValue() diff --git a/driver/src/test/java/org/neo4j/driver/internal/value/StringValueTest.java b/driver/src/test/java/org/neo4j/driver/internal/value/StringValueTest.java index 3cdd84f958..dc2b4ccc28 100644 --- a/driver/src/test/java/org/neo4j/driver/internal/value/StringValueTest.java +++ b/driver/src/test/java/org/neo4j/driver/internal/value/StringValueTest.java @@ -22,8 +22,8 @@ import org.neo4j.driver.internal.types.InternalTypeSystem; import org.neo4j.driver.internal.types.TypeConstructor; -import org.neo4j.driver.v1.types.TypeSystem; import org.neo4j.driver.v1.Value; +import org.neo4j.driver.v1.types.TypeSystem; import static org.hamcrest.CoreMatchers.equalTo; import static org.hamcrest.CoreMatchers.notNullValue; @@ -86,7 +86,7 @@ public void shouldNotBeNull() public void shouldTypeAsString() { InternalValue value = new StringValue( "Spongebob" ); - assertThat( value.typeConstructor(), equalTo( TypeConstructor.STRING_TyCon ) ); + assertThat( value.typeConstructor(), equalTo( TypeConstructor.STRING ) ); } @Test diff --git a/driver/src/test/java/org/neo4j/driver/internal/value/TimeValueTest.java b/driver/src/test/java/org/neo4j/driver/internal/value/TimeValueTest.java new file mode 100644 index 0000000000..315a843775 --- /dev/null +++ b/driver/src/test/java/org/neo4j/driver/internal/value/TimeValueTest.java @@ -0,0 +1,73 @@ +/* + * Copyright (c) 2002-2018 "Neo Technology," + * Network Engine for Objects in Lund AB [http://neotechnology.com] + * + * This file is part of Neo4j. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.neo4j.driver.internal.value; + +import org.junit.Test; + +import java.time.OffsetTime; +import java.time.ZoneOffset; + +import org.neo4j.driver.internal.types.InternalTypeSystem; +import org.neo4j.driver.v1.exceptions.value.Uncoercible; + +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.fail; + +public class TimeValueTest +{ + @Test + public void shouldHaveCorrectType() + { + OffsetTime time = OffsetTime.now().withOffsetSameInstant( ZoneOffset.ofHoursMinutes( 5, 30 ) ); + TimeValue timeValue = new TimeValue( time ); + assertEquals( InternalTypeSystem.TYPE_SYSTEM.TIME(), timeValue.type() ); + } + + @Test + public void shouldSupportAsObject() + { + OffsetTime time = OffsetTime.of( 19, 0, 10, 1, ZoneOffset.ofHours( -3 ) ); + TimeValue timeValue = new TimeValue( time ); + assertEquals( time, timeValue.asObject() ); + } + + @Test + public void shouldSupportAsOffsetTime() + { + OffsetTime time = OffsetTime.of( 23, 59, 59, 999_999_999, ZoneOffset.ofHoursMinutes( 2, 15 ) ); + TimeValue timeValue = new TimeValue( time ); + assertEquals( time, timeValue.asOffsetTime() ); + } + + @Test + public void shouldNotSupportAsLong() + { + OffsetTime time = OffsetTime.now().withOffsetSameInstant( ZoneOffset.ofHours( -5 ) ); + TimeValue timeValue = new TimeValue( time ); + + try + { + timeValue.asLong(); + fail( "Exception expected" ); + } + catch ( Uncoercible ignore ) + { + } + } +} diff --git a/driver/src/test/java/org/neo4j/driver/v1/integration/PointsIT.java b/driver/src/test/java/org/neo4j/driver/v1/integration/SpatialTypesIT.java similarity index 98% rename from driver/src/test/java/org/neo4j/driver/v1/integration/PointsIT.java rename to driver/src/test/java/org/neo4j/driver/v1/integration/SpatialTypesIT.java index c6e92a52a8..b65d05e3b4 100644 --- a/driver/src/test/java/org/neo4j/driver/v1/integration/PointsIT.java +++ b/driver/src/test/java/org/neo4j/driver/v1/integration/SpatialTypesIT.java @@ -40,7 +40,7 @@ import static org.neo4j.driver.v1.Values.ofPoint2D; import static org.neo4j.driver.v1.Values.point2D; -public class PointsIT +public class SpatialTypesIT { private static final long WGS_84_CRS_CODE = 4326; private static final long CARTESIAN_CRS_CODE = 7203; @@ -106,7 +106,7 @@ public void shouldSendAndReceiveRandom2DPointArrays() { Stream> randomPointLists = ThreadLocalRandom.current() .ints( 1_000, 0, 2 ) - .mapToObj( PointsIT::randomPoint2DList ); + .mapToObj( SpatialTypesIT::randomPoint2DList ); randomPointLists.forEach( this::testPoint2DListSendAndReceive ); } diff --git a/driver/src/test/java/org/neo4j/driver/v1/integration/TemporalTypesIT.java b/driver/src/test/java/org/neo4j/driver/v1/integration/TemporalTypesIT.java new file mode 100644 index 0000000000..67a2b14900 --- /dev/null +++ b/driver/src/test/java/org/neo4j/driver/v1/integration/TemporalTypesIT.java @@ -0,0 +1,282 @@ +/* + * Copyright (c) 2002-2018 "Neo Technology," + * Network Engine for Objects in Lund AB [http://neotechnology.com] + * + * This file is part of Neo4j. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.neo4j.driver.v1.integration; + +import org.junit.Before; +import org.junit.Rule; +import org.junit.Test; + +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.function.Supplier; + +import org.neo4j.driver.v1.Record; +import org.neo4j.driver.v1.Value; +import org.neo4j.driver.v1.types.IsoDuration; +import org.neo4j.driver.v1.util.Function; +import org.neo4j.driver.v1.util.TemporalUtil; +import org.neo4j.driver.v1.util.TestNeo4jSession; + +import static java.time.Month.MARCH; +import static java.util.Collections.singletonMap; +import static org.junit.Assert.assertEquals; +import static org.junit.Assume.assumeTrue; +import static org.neo4j.driver.internal.util.ServerVersion.v3_4_0; +import static org.neo4j.driver.v1.Values.isoDuration; + +public class TemporalTypesIT +{ + private static final int RANDOM_VALUES_TO_TEST = 1_000; + + @Rule + public final TestNeo4jSession session = new TestNeo4jSession(); + + @Before + public void setUp() + { + assumeTrue( session.version().greaterThanOrEqual( v3_4_0 ) ); + } + + @Test + public void shouldSendDate() + { + testSendValue( LocalDate.now(), Value::asLocalDate ); + } + + @Test + public void shouldReceiveDate() + { + testReceiveValue( "RETURN date({year: 1995, month: 12, day: 4})", + LocalDate.of( 1995, 12, 4 ), + Value::asLocalDate ); + } + + @Test + public void shouldSendAndReceiveDate() + { + testSendAndReceiveValue( LocalDate.now(), Value::asLocalDate ); + } + + @Test + public void shouldSendAndReceiveRandomDate() + { + testSendAndReceiveRandomValues( TemporalUtil::randomLocalDate, Value::asLocalDate ); + } + + @Test + public void shouldSendTime() + { + testSendValue( OffsetTime.now(), Value::asOffsetTime ); + } + + @Test + public void shouldReceiveTime() + { + testReceiveValue( "RETURN time({hour: 23, minute: 19, second: 55, timezone:'-07:00'})", + OffsetTime.of( 23, 19, 55, 0, ZoneOffset.ofHours( -7 ) ), + Value::asOffsetTime ); + } + + @Test + public void shouldSendAndReceiveTime() + { + testSendAndReceiveValue( OffsetTime.now(), Value::asOffsetTime ); + } + + @Test + public void shouldSendAndReceiveRandomTime() + { + testSendAndReceiveRandomValues( TemporalUtil::randomOffsetTime, Value::asOffsetTime ); + } + + @Test + public void shouldSendLocalTime() + { + testSendValue( LocalTime.now(), Value::asLocalTime ); + } + + @Test + public void shouldReceiveLocalTime() + { + testReceiveValue( "RETURN localtime({hour: 22, minute: 59, second: 10, nanosecond: 999999})", + LocalTime.of( 22, 59, 10, 999_999 ), + Value::asLocalTime ); + } + + @Test + public void shouldSendAndReceiveLocalTime() + { + testSendAndReceiveValue( LocalTime.now(), Value::asLocalTime ); + } + + @Test + public void shouldSendAndReceiveRandomLocalTime() + { + testSendAndReceiveRandomValues( TemporalUtil::randomLocalTime, Value::asLocalTime ); + } + + @Test + public void shouldSendLocalDateTime() + { + testSendValue( LocalDateTime.now(), Value::asLocalDateTime ); + } + + @Test + public void shouldReceiveLocalDateTime() + { + testReceiveValue( "RETURN localdatetime({year: 1899, month: 3, day: 20, hour: 12, minute: 17, second: 13, nanosecond: 999})", + LocalDateTime.of( 1899, MARCH, 20, 12, 17, 13, 999 ), + Value::asLocalDateTime ); + } + + @Test + public void shouldSendAndReceiveLocalDateTime() + { + testSendAndReceiveValue( LocalDateTime.now(), Value::asLocalDateTime ); + } + + @Test + public void shouldSendAndReceiveRandomLocalDateTime() + { + testSendAndReceiveRandomValues( TemporalUtil::randomLocalDateTime, Value::asLocalDateTime ); + } + + @Test + public void shouldSendDateTimeWithZoneOffset() + { + ZoneOffset offset = ZoneOffset.ofHoursMinutes( -4, -15 ); + testSendValue( ZonedDateTime.of( 1845, 3, 25, 19, 15, 45, 22, offset ), Value::asZonedDateTime ); + } + + @Test + public void shouldReceiveDateTimeWithZoneOffset() + { + ZoneOffset offset = ZoneOffset.ofHoursMinutes( 3, 30 ); + testReceiveValue( "RETURN datetime({year:1984, month:10, day:11, hour:21, minute:30, second:34, timezone:'+03:30'})", + ZonedDateTime.of( 1984, 10, 11, 21, 30, 34, 0, offset ), + Value::asZonedDateTime ); + } + + @Test + public void shouldSendAndReceiveDateTimeWithZoneOffset() + { + ZoneOffset offset = ZoneOffset.ofHoursMinutes( -7, -15 ); + testSendAndReceiveValue( ZonedDateTime.of( 2017, 3, 9, 11, 12, 13, 14, offset ), Value::asZonedDateTime ); + } + + @Test + public void shouldSendAndReceiveRandomDateTimeWithZoneOffset() + { + testSendAndReceiveRandomValues( TemporalUtil::randomZonedDateTimeWithOffset, Value::asZonedDateTime ); + } + + @Test + public void shouldSendDateTimeWithZoneId() + { + ZoneId zoneId = ZoneId.of( "Europe/Stockholm" ); + testSendValue( ZonedDateTime.of( 2049, 9, 11, 19, 10, 40, 20, zoneId ), Value::asZonedDateTime ); + } + + @Test + public void shouldReceiveDateTimeWithZoneId() + { + ZoneId zoneId = ZoneId.of( "Europe/London" ); + testReceiveValue( "RETURN datetime({year:2000, month:1, day:1, hour:9, minute:5, second:1, timezone:'Europe/London'})", + ZonedDateTime.of( 2000, 1, 1, 9, 5, 1, 0, zoneId ), + Value::asZonedDateTime ); + } + + @Test + public void shouldSendAndReceiveDateTimeWithZoneId() + { + ZoneId zoneId = ZoneId.of( "Europe/Stockholm" ); + testSendAndReceiveValue( ZonedDateTime.of( 2099, 12, 29, 12, 59, 59, 59, zoneId ), Value::asZonedDateTime ); + } + + @Test + public void shouldSendAndReceiveRandomDateTimeWithZoneId() + { + testSendAndReceiveRandomValues( TemporalUtil::randomZonedDateTimeWithZoneId, Value::asZonedDateTime ); + } + + @Test + public void shouldSendDuration() + { + testSendValue( newDuration( 8, 12, 90, 8 ), Value::asIsoDuration ); + } + + @Test + public void shouldReceiveDuration() + { + testReceiveValue( "RETURN duration({months: 13, days: 40, seconds: 12, nanoseconds: 999})", + newDuration( 13, 40, 12, 999 ), + Value::asIsoDuration ); + } + + @Test + public void shouldSendAndReceiveDuration() + { + testSendAndReceiveValue( newDuration( 7, 7, 88, 999_999 ), Value::asIsoDuration ); + } + + @Test + public void shouldSendAndReceiveRandomDuration() + { + testSendAndReceiveRandomValues( TemporalUtil::randomDuration, Value::asIsoDuration ); + } + + private void testSendAndReceiveRandomValues( Supplier supplier, Function converter ) + { + for ( int i = 0; i < RANDOM_VALUES_TO_TEST; i++ ) + { + testSendAndReceiveValue( supplier.get(), converter ); + } + } + + private void testSendValue( T value, Function converter ) + { + Record record1 = session.run( "CREATE (n:Node {value: $value}) RETURN 42", singletonMap( "value", value ) ).single(); + assertEquals( 42, record1.get( 0 ).asInt() ); + + Record record2 = session.run( "MATCH (n:Node) RETURN n.value" ).single(); + assertEquals( value, converter.apply( record2.get( 0 ) ) ); + } + + private void testReceiveValue( String query, T expectedValue, Function converter ) + { + Record record = session.run( query ).single(); + assertEquals( expectedValue, converter.apply( record.get( 0 ) ) ); + } + + private void testSendAndReceiveValue( T value, Function converter ) + { + Record record = session.run( "CREATE (n:Node {value: $value}) RETURN n.value", singletonMap( "value", value ) ).single(); + assertEquals( value, converter.apply( record.get( 0 ) ) ); + } + + private static IsoDuration newDuration( long months, long days, long seconds, long nanoseconds ) + { + return isoDuration( months, days, seconds, nanoseconds ).asIsoDuration(); + } +} diff --git a/driver/src/test/java/org/neo4j/driver/v1/tck/tck/util/TestRelationshipValue.java b/driver/src/test/java/org/neo4j/driver/v1/tck/tck/util/TestRelationshipValue.java index 2029161a59..5cfe5edff7 100644 --- a/driver/src/test/java/org/neo4j/driver/v1/tck/tck/util/TestRelationshipValue.java +++ b/driver/src/test/java/org/neo4j/driver/v1/tck/tck/util/TestRelationshipValue.java @@ -22,9 +22,9 @@ import org.neo4j.driver.internal.InternalRelationship; import org.neo4j.driver.internal.value.RelationshipValue; +import org.neo4j.driver.v1.Value; import org.neo4j.driver.v1.types.Entity; import org.neo4j.driver.v1.types.Relationship; -import org.neo4j.driver.v1.Value; public class TestRelationshipValue extends RelationshipValue implements Entity { diff --git a/driver/src/test/java/org/neo4j/driver/v1/util/TemporalUtil.java b/driver/src/test/java/org/neo4j/driver/v1/util/TemporalUtil.java new file mode 100644 index 0000000000..7096780caa --- /dev/null +++ b/driver/src/test/java/org/neo4j/driver/v1/util/TemporalUtil.java @@ -0,0 +1,138 @@ +/* + * Copyright (c) 2002-2018 "Neo Technology," + * Network Engine for Objects in Lund AB [http://neotechnology.com] + * + * This file is part of Neo4j. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.neo4j.driver.v1.util; + +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.time.temporal.ChronoField; +import java.time.temporal.ValueRange; +import java.util.Set; +import java.util.concurrent.ThreadLocalRandom; + +import org.neo4j.driver.internal.InternalIsoDuration; +import org.neo4j.driver.v1.types.IsoDuration; + +import static java.time.temporal.ChronoField.DAY_OF_MONTH; +import static java.time.temporal.ChronoField.HOUR_OF_DAY; +import static java.time.temporal.ChronoField.MINUTE_OF_HOUR; +import static java.time.temporal.ChronoField.MONTH_OF_YEAR; +import static java.time.temporal.ChronoField.NANO_OF_SECOND; +import static java.time.temporal.ChronoField.SECOND_OF_MINUTE; +import static java.time.temporal.ChronoField.YEAR; + +public final class TemporalUtil +{ + private TemporalUtil() + { + } + + public static LocalDate randomLocalDate() + { + return LocalDate.of( random( YEAR ), random( MONTH_OF_YEAR ), random( DAY_OF_MONTH ) ); + } + + public static OffsetTime randomOffsetTime() + { + ZoneOffset offset = randomZoneOffset(); + return OffsetTime.of( random( HOUR_OF_DAY ), random( MINUTE_OF_HOUR ), random( SECOND_OF_MINUTE ), random( NANO_OF_SECOND ), offset ); + } + + public static LocalTime randomLocalTime() + { + return LocalTime.of( random( HOUR_OF_DAY ), random( MINUTE_OF_HOUR ), random( SECOND_OF_MINUTE ), random( NANO_OF_SECOND ) ); + } + + public static LocalDateTime randomLocalDateTime() + { + return LocalDateTime.of( random( YEAR ), random( MONTH_OF_YEAR ), random( DAY_OF_MONTH ), random( HOUR_OF_DAY ), + random( MINUTE_OF_HOUR ), random( SECOND_OF_MINUTE ), random( NANO_OF_SECOND ) ); + } + + public static ZonedDateTime randomZonedDateTimeWithOffset() + { + return randomZonedDateTime( randomZoneOffset() ); + } + + public static ZonedDateTime randomZonedDateTimeWithZoneId() + { + return randomZonedDateTime( randomZoneId() ); + } + + public static IsoDuration randomDuration() + { + int sign = random().nextBoolean() ? 1 : -1; // duration can be negative + return new InternalIsoDuration( sign * randomInt(), sign * randomInt(), sign * randomInt(), sign * Math.abs( random( NANO_OF_SECOND ) ) ); + } + + private static ZonedDateTime randomZonedDateTime( ZoneId zoneId ) + { + return ZonedDateTime.of( random( YEAR ), random( MONTH_OF_YEAR ), random( DAY_OF_MONTH ), random( HOUR_OF_DAY ), + random( MINUTE_OF_HOUR ), random( SECOND_OF_MINUTE ), random( NANO_OF_SECOND ), zoneId ); + } + + private static ZoneOffset randomZoneOffset() + { + int min = ZoneOffset.MIN.getTotalSeconds(); + int max = ZoneOffset.MAX.getTotalSeconds(); + return ZoneOffset.ofTotalSeconds( random().nextInt( min, max ) ); + } + + private static ZoneId randomZoneId() + { + Set availableZoneIds = ZoneId.getAvailableZoneIds(); + int randomIndex = random().nextInt( availableZoneIds.size() ); + int index = 0; + for ( String id : availableZoneIds ) + { + if ( index == randomIndex ) + { + return ZoneId.of( id ); + } + else + { + index++; + } + } + throw new AssertionError( "Unable to pick random ZoneId from the set of available ids: " + availableZoneIds ); + } + + private static int random( ChronoField field ) + { + ValueRange range = field.range(); + long min = range.getMinimum(); + long max = range.getSmallestMaximum(); + long value = random().nextLong( min, max ); + return Math.toIntExact( value ); + } + + private static int randomInt() + { + return random().nextInt( 0, Integer.MAX_VALUE ); + } + + private static ThreadLocalRandom random() + { + return ThreadLocalRandom.current(); + } +}