Skip to content

Commit

Permalink
Access temporal values in ValueWriter
Browse files Browse the repository at this point in the history
Instead of their serialized form. This makes it easier for different
`ValueWriter` implementations to use different serialization strategies.

For example property store and schema index convert zoned date and time
to UTC. This makes it convenient to compare time/date-time. When Bolt
server uses same serialization it forces all clients (driver) to convert
from and to UTC, which is especially problematic for DateTime containing
timezone ID. After this change Bolt server should be able to send and
receive local time/date-time with timezone information without doing
double conversion. No conversion would then happen in clients.
  • Loading branch information
lutovich committed Mar 27, 2018
1 parent ddc0a68 commit 899dbf4
Show file tree
Hide file tree
Showing 27 changed files with 552 additions and 303 deletions.
Expand Up @@ -20,6 +20,11 @@
package org.neo4j.bolt.v1.messaging;

import java.io.IOException;
import java.time.LocalDate;
import java.time.LocalDateTime;
import java.time.LocalTime;
import java.time.OffsetTime;
import java.time.ZonedDateTime;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
Expand Down Expand Up @@ -299,37 +304,31 @@ public void writeDuration( long months, long days, long seconds, int nanos ) thr
}

@Override
public void writeDate( long epochDay ) throws IOException
public void writeDate( LocalDate localDate ) throws IOException
{
throw new BoltIOException( Status.Request.Invalid, "Date is not yet supported as a return type in Bolt" );
}

@Override
public void writeLocalTime( long nanoOfDay ) throws IOException
public void writeLocalTime( LocalTime localTime ) throws IOException
{
throw new BoltIOException( Status.Request.Invalid, "LocalTime is not yet supported as a return type in Bolt" );
}

@Override
public void writeTime( long nanosOfDayUTC, int offsetSeconds ) throws IOException
public void writeTime( OffsetTime offsetTime ) throws IOException
{
throw new BoltIOException( Status.Request.Invalid, "Time is not yet supported as a return type in Bolt" );
}

@Override
public void writeLocalDateTime( long epochSecond, int nano ) throws IOException
public void writeLocalDateTime( LocalDateTime localDateTime ) throws IOException
{
throw new BoltIOException( Status.Request.Invalid, "LocalDateTime is not yet supported as a return type in Bolt" );
}

@Override
public void writeDateTime( long epochSecondUTC, int nano, int offsetSeconds ) throws IOException
{
throw new BoltIOException( Status.Request.Invalid, "DateTime is not yet supported as a return type in Bolt" );
}

@Override
public void writeDateTime( long epochSecondUTC, int nano, String zoneId ) throws IOException
public void writeDateTime( ZonedDateTime zonedDateTime ) throws IOException
{
throw new BoltIOException( Status.Request.Invalid, "DateTime is not yet supported as a return type in Bolt" );
}
Expand Down
Expand Up @@ -20,8 +20,13 @@
package org.neo4j.bolt.v2.messaging;

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.Arrays;

import org.neo4j.bolt.v1.messaging.Neo4jPack;
Expand All @@ -36,9 +41,10 @@
import org.neo4j.values.storable.LocalDateTimeValue;
import org.neo4j.values.storable.LocalTimeValue;
import org.neo4j.values.storable.PointValue;
import org.neo4j.values.storable.TimeUtil;
import org.neo4j.values.storable.TimeValue;
import org.neo4j.values.utils.TemporalUtil;

import static java.time.ZoneOffset.UTC;
import static org.neo4j.values.storable.DateTimeValue.datetime;
import static org.neo4j.values.storable.DateValue.epochDate;
import static org.neo4j.values.storable.DurationValue.duration;
Expand Down Expand Up @@ -123,51 +129,70 @@ public void writeDuration( long months, long days, long seconds, int nanos ) thr
}

@Override
public void writeDate( long epochDay ) throws IOException
public void writeDate( LocalDate localDate ) throws IOException
{
long epochDay = localDate.toEpochDay();

packStructHeader( 1, DATE );
pack( epochDay );
}

@Override
public void writeLocalTime( long nanoOfDay ) throws IOException
public void writeLocalTime( LocalTime localTime ) throws IOException
{
long nanoOfDay = localTime.toNanoOfDay();

packStructHeader( 1, LOCAL_TIME );
pack( nanoOfDay );
}

@Override
public void writeTime( long nanosOfDayUTC, int offsetSeconds ) throws IOException
public void writeTime( OffsetTime offsetTime ) throws IOException
{
long nanosOfDayLocal = offsetTime.toLocalTime().toNanoOfDay();
int offsetSeconds = offsetTime.getOffset().getTotalSeconds();

packStructHeader( 2, TIME );
pack( TimeUtil.nanosOfDayToLocal( nanosOfDayUTC, offsetSeconds ) );
pack( nanosOfDayLocal );
pack( offsetSeconds );
}

@Override
public void writeLocalDateTime( long epochSecond, int nano ) throws IOException
public void writeLocalDateTime( LocalDateTime localDateTime ) throws IOException
{
long epochSecond = localDateTime.toEpochSecond( UTC );
int nano = localDateTime.getNano();

packStructHeader( 2, LOCAL_DATE_TIME );
pack( epochSecond );
pack( nano );
}

@Override
public void writeDateTime( long epochSecondUTC, int nano, int offsetSeconds ) throws IOException
public void writeDateTime( ZonedDateTime zonedDateTime ) throws IOException
{
packStructHeader( 3, DATE_TIME_WITH_ZONE_OFFSET );
pack( epochSecondUTC );
pack( nano );
pack( offsetSeconds );
}
long epochSecondUTC = zonedDateTime.toEpochSecond();
int nano = zonedDateTime.getNano();

@Override
public void writeDateTime( long epochSecondUTC, int nano, String zoneId ) throws IOException
{
packStructHeader( 3, DATE_TIME_WITH_ZONE_NAME );
pack( epochSecondUTC );
pack( nano );
pack( zoneId );
ZoneId zone = zonedDateTime.getZone();
if ( zone instanceof ZoneOffset )
{
int offsetSeconds = ((ZoneOffset) zone).getTotalSeconds();

packStructHeader( 3, DATE_TIME_WITH_ZONE_OFFSET );
pack( epochSecondUTC );
pack( nano );
pack( offsetSeconds );
}
else
{
String zoneId = zone.getId();

packStructHeader( 3, DATE_TIME_WITH_ZONE_NAME );
pack( epochSecondUTC );
pack( nano );
pack( zoneId );
}
}
}

Expand Down Expand Up @@ -247,7 +272,7 @@ private TimeValue unpackTime() throws IOException
{
long nanosOfDayLocal = unpackLong();
int offsetSeconds = unpackInteger();
return time( TimeUtil.nanosOfDayToUTC( nanosOfDayLocal, offsetSeconds ), ZoneOffset.ofTotalSeconds( offsetSeconds ) );
return time( TemporalUtil.nanosOfDayToUTC( nanosOfDayLocal, offsetSeconds ), ZoneOffset.ofTotalSeconds( offsetSeconds ) );
}

private LocalDateTimeValue unpackLocalDateTime() throws IOException
Expand Down
Expand Up @@ -20,13 +20,10 @@
package org.neo4j.cypher.internal.codegen;

import java.lang.reflect.Array;
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 java.util.ArrayDeque;
import java.util.ArrayList;
Expand All @@ -47,13 +44,11 @@
import org.neo4j.values.storable.DurationValue;
import org.neo4j.values.storable.TextArray;
import org.neo4j.values.storable.TextValue;
import org.neo4j.values.storable.TimeUtil;
import org.neo4j.values.storable.Values;
import org.neo4j.values.virtual.MapValue;
import org.neo4j.values.virtual.NodeValue;
import org.neo4j.values.virtual.RelationshipValue;

import static java.time.ZoneOffset.UTC;
import static org.neo4j.helpers.collection.Iterators.iteratorsEqual;

/**
Expand Down Expand Up @@ -365,41 +360,33 @@ public void writeDuration( long months, long days, long seconds, int nanos )
}

@Override
public void writeDate( long epochDay )
public void writeDate( LocalDate localDate )
{
writeValue( LocalDate.ofEpochDay( epochDay ) );
writeValue( localDate );
}

@Override
public void writeLocalTime( long nanoOfDay )
public void writeLocalTime( LocalTime localTime )
{
writeValue( LocalTime.ofNanoOfDay( nanoOfDay ) );
writeValue( localTime );
}

@Override
public void writeTime( long nanosOfDayUTC, int offsetSeconds )
public void writeTime( OffsetTime offsetTime )
{
writeValue( OffsetTime.of(
LocalTime.ofNanoOfDay( TimeUtil.nanosOfDayToLocal( nanosOfDayUTC, offsetSeconds ) ),
ZoneOffset.ofTotalSeconds( offsetSeconds ) ) );
writeValue( offsetTime );
}

@Override
public void writeLocalDateTime( long epochSecond, int nano )
public void writeLocalDateTime( LocalDateTime localDateTime )
{
writeValue( LocalDateTime.ofInstant( Instant.ofEpochSecond( epochSecond, nano ), UTC ) );
writeValue( localDateTime );
}

@Override
public void writeDateTime( long epochSecondUTC, int nano, int offsetSeconds )
public void writeDateTime( ZonedDateTime zonedDateTime )
{
writeValue( ZonedDateTime.ofInstant( Instant.ofEpochSecond( epochSecondUTC, nano ), ZoneOffset.ofTotalSeconds( offsetSeconds ) ) );
}

@Override
public void writeDateTime( long epochSecondUTC, int nano, String zoneId )
{
writeValue( ZonedDateTime.ofInstant( Instant.ofEpochSecond( epochSecondUTC, nano ), ZoneId.of( zoneId ) ) );
writeValue( zonedDateTime );
}

private interface Writer
Expand Down
Expand Up @@ -241,23 +241,20 @@ object CastSupport {
override def writeDuration(months: Long, days: Long, seconds: Long, nanos: Int): Unit =
write(DurationValue.duration(months, days, seconds, nanos))

override def writeDate(epochDay: Long): Unit =
write(DateValue.epochDate(epochDay).asObject())
override def writeDate(localDate: LocalDate): Unit =
write(localDate)

override def writeLocalTime(nanoOfDay: Long): Unit =
write(LocalTimeValue.localTime(nanoOfDay).asObject())
override def writeLocalTime(localTime: LocalTime): Unit =
write(localTime)

override def writeTime(nanosOfDayUTC: Long, offsetSeconds: Int): Unit =
write(TimeValue.time(nanosOfDayUTC, ZoneOffset.ofTotalSeconds(offsetSeconds)).asObject())
override def writeTime(offsetTime: OffsetTime): Unit =
write(offsetTime)

override def writeLocalDateTime(epochSecond: Long, nano: Int): Unit =
write(LocalDateTimeValue.localDateTime(epochSecond,nano).asObject())
override def writeLocalDateTime(localDateTime: LocalDateTime): Unit =
write(localDateTime)

override def writeDateTime(epochSecondUTC: Long, nano: Int, offsetSeconds: Int): Unit =
write(DateTimeValue.datetime(epochSecondUTC, nano, ZoneOffset.ofTotalSeconds(offsetSeconds)).asObject())

override def writeDateTime(epochSecondUTC: Long, nano: Int, zoneId: String): Unit =
write(DateTimeValue.datetime(epochSecondUTC, nano, ZoneId.of(zoneId)).asObject())
override def writeDateTime(zonedDateTime: ZonedDateTime): Unit =
write(zonedDateTime)
}

}
Expand Up @@ -19,8 +19,11 @@
*/
package org.neo4j.kernel.api.index;

import java.time.ZoneId;
import java.time.ZoneOffset;
import java.time.LocalDate;
import java.time.LocalDateTime;
import java.time.LocalTime;
import java.time.OffsetTime;
import java.time.ZonedDateTime;
import java.util.Base64;

import org.neo4j.string.UTF8;
Expand Down Expand Up @@ -166,44 +169,37 @@ public void writeDuration( long months, long days, long seconds, int nanos ) thr
}

@Override
public void writeDate( long epochDay ) throws RuntimeException
public void writeDate( LocalDate localDate ) throws RuntimeException
{
builder.append( DateValue.epochDate( epochDay ).prettyPrint() );
builder.append( DateValue.date( localDate ).prettyPrint() );
builder.append( '|' );
}

@Override
public void writeLocalTime( long nanoOfDay ) throws RuntimeException
public void writeLocalTime( LocalTime localTime ) throws RuntimeException
{
builder.append( LocalTimeValue.localTime( nanoOfDay ).prettyPrint() );
builder.append( LocalTimeValue.localTime( localTime ).prettyPrint() );
builder.append( '|' );
}

@Override
public void writeTime( long nanosOfDayUTC, int offsetSeconds ) throws RuntimeException
public void writeTime( OffsetTime offsetTime ) throws RuntimeException
{
builder.append( TimeValue.time( nanosOfDayUTC, ZoneOffset.ofTotalSeconds( offsetSeconds ) ).prettyPrint() );
builder.append( TimeValue.time( offsetTime ).prettyPrint() );
builder.append( '|' );
}

@Override
public void writeLocalDateTime( long epochSecond, int nano ) throws RuntimeException
public void writeLocalDateTime( LocalDateTime localDateTime ) throws RuntimeException
{
builder.append( LocalDateTimeValue.localDateTime( epochSecond, nano ).prettyPrint() );
builder.append( LocalDateTimeValue.localDateTime( localDateTime ).prettyPrint() );
builder.append( '|' );
}

@Override
public void writeDateTime( long epochSecondUTC, int nano, int offsetSeconds ) throws RuntimeException
public void writeDateTime( ZonedDateTime zonedDateTime ) throws RuntimeException
{
builder.append( DateTimeValue.datetime( epochSecondUTC, nano, ZoneOffset.ofTotalSeconds( offsetSeconds ) ).prettyPrint() );
builder.append( '|' );
}

@Override
public void writeDateTime( long epochSecondUTC, int nano, String zoneId ) throws RuntimeException
{
builder.append( DateTimeValue.datetime( epochSecondUTC, nano, ZoneId.of( zoneId ) ).prettyPrint() );
builder.append( DateTimeValue.datetime( zonedDateTime ).prettyPrint() );
builder.append( '|' );
}

Expand Down
Expand Up @@ -20,15 +20,15 @@
package org.neo4j.kernel.impl.index.schema;

import org.neo4j.index.internal.gbptree.GBPTree;
import org.neo4j.kernel.impl.store.TemporalValueWriterAdapter;
import org.neo4j.values.storable.Value;
import org.neo4j.values.storable.ValueWriter;

/**
* Includes value and entity id (to be able to handle non-unique values).
* This is the abstraction of what NativeSchemaIndex with friends need from a schema key.
* Note that it says nothing about how keys are compared, serialized, read, written, etc. That is the job of Layout.
*/
abstract class NativeSchemaKey<SELF extends NativeSchemaKey<SELF>> extends ValueWriter.Adapter<RuntimeException>
abstract class NativeSchemaKey<SELF extends NativeSchemaKey<SELF>> extends TemporalValueWriterAdapter<RuntimeException>
{
private static final boolean DEFAULT_COMPARE_ID = true;

Expand Down
Expand Up @@ -53,7 +53,6 @@
import org.neo4j.values.storable.ArrayValue;
import org.neo4j.values.storable.CoordinateReferenceSystem;
import org.neo4j.values.storable.Value;
import org.neo4j.values.storable.ValueWriter;

import static org.neo4j.kernel.impl.store.DynamicArrayStore.getRightArray;
import static org.neo4j.kernel.impl.store.NoStoreHeaderFormat.NO_STORE_HEADER_FORMAT;
Expand Down Expand Up @@ -411,7 +410,7 @@ private static ByteBuffer grow( ByteBuffer buffer, int required )
return ByteBuffer.allocate( capacity ).order( ByteOrder.LITTLE_ENDIAN ).put( buffer );
}

private static class PropertyBlockValueWriter implements ValueWriter<IllegalArgumentException>
private static class PropertyBlockValueWriter extends TemporalValueWriterAdapter<IllegalArgumentException>
{
private final PropertyBlock block;
private final int keyId;
Expand Down

0 comments on commit 899dbf4

Please sign in to comment.