diff --git a/driver/src/main/java/org/neo4j/driver/internal/async/connection/ChannelAttributes.java b/driver/src/main/java/org/neo4j/driver/internal/async/connection/ChannelAttributes.java index 1171244340..6ae0d80d72 100644 --- a/driver/src/main/java/org/neo4j/driver/internal/async/connection/ChannelAttributes.java +++ b/driver/src/main/java/org/neo4j/driver/internal/async/connection/ChannelAttributes.java @@ -22,9 +22,13 @@ import io.netty.channel.Channel; import io.netty.util.AttributeKey; +import java.util.Collections; +import java.util.HashSet; import java.util.Optional; +import java.util.Set; import org.neo4j.driver.internal.BoltServerAddress; import org.neo4j.driver.internal.async.inbound.InboundMessageDispatcher; +import org.neo4j.driver.internal.messaging.BoltPatchesListener; import org.neo4j.driver.internal.messaging.BoltProtocolVersion; public final class ChannelAttributes { @@ -39,6 +43,8 @@ public final class ChannelAttributes { private static final AttributeKey TERMINATION_REASON = newInstance("terminationReason"); private static final AttributeKey AUTHORIZATION_STATE_LISTENER = newInstance("authorizationStateListener"); + private static final AttributeKey> BOLT_PATCHES_LISTENERS = + newInstance("boltPatchesListeners"); // configuration hints provided by the server private static final AttributeKey CONNECTION_READ_TIMEOUT = newInstance("connectionReadTimeout"); @@ -134,6 +140,20 @@ public static void setConnectionReadTimeout(Channel channel, Long connectionRead setOnce(channel, CONNECTION_READ_TIMEOUT, connectionReadTimeout); } + public static void addBoltPatchesListener(Channel channel, BoltPatchesListener listener) { + Set boltPatchesListeners = get(channel, BOLT_PATCHES_LISTENERS); + if (boltPatchesListeners == null) { + boltPatchesListeners = new HashSet<>(); + setOnce(channel, BOLT_PATCHES_LISTENERS, boltPatchesListeners); + } + boltPatchesListeners.add(listener); + } + + public static Set boltPatchesListeners(Channel channel) { + Set boltPatchesListeners = get(channel, BOLT_PATCHES_LISTENERS); + return boltPatchesListeners != null ? boltPatchesListeners : Collections.emptySet(); + } + private static T get(Channel channel, AttributeKey key) { return channel.attr(key).get(); } diff --git a/driver/src/main/java/org/neo4j/driver/internal/async/connection/ChannelPipelineBuilderImpl.java b/driver/src/main/java/org/neo4j/driver/internal/async/connection/ChannelPipelineBuilderImpl.java index 0d9869dccd..cea52e8c1e 100644 --- a/driver/src/main/java/org/neo4j/driver/internal/async/connection/ChannelPipelineBuilderImpl.java +++ b/driver/src/main/java/org/neo4j/driver/internal/async/connection/ChannelPipelineBuilderImpl.java @@ -18,6 +18,9 @@ */ package org.neo4j.driver.internal.async.connection; +import static org.neo4j.driver.internal.async.connection.ChannelAttributes.addBoltPatchesListener; + +import io.netty.channel.Channel; import io.netty.channel.ChannelPipeline; import org.neo4j.driver.Logging; import org.neo4j.driver.internal.async.inbound.ChannelErrorHandler; @@ -33,10 +36,15 @@ public void build(MessageFormat messageFormat, ChannelPipeline pipeline, Logging // inbound handlers pipeline.addLast(new ChunkDecoder(logging)); pipeline.addLast(new MessageDecoder()); - pipeline.addLast(new InboundMessageHandler(messageFormat, logging)); + Channel channel = pipeline.channel(); + InboundMessageHandler inboundMessageHandler = new InboundMessageHandler(messageFormat, logging); + addBoltPatchesListener(channel, inboundMessageHandler); + pipeline.addLast(inboundMessageHandler); // outbound handlers - pipeline.addLast(OutboundMessageHandler.NAME, new OutboundMessageHandler(messageFormat, logging)); + OutboundMessageHandler outboundMessageHandler = new OutboundMessageHandler(messageFormat, logging); + addBoltPatchesListener(channel, outboundMessageHandler); + pipeline.addLast(OutboundMessageHandler.NAME, outboundMessageHandler); // last one - error handler pipeline.addLast(new ChannelErrorHandler(logging)); diff --git a/driver/src/main/java/org/neo4j/driver/internal/async/inbound/InboundMessageHandler.java b/driver/src/main/java/org/neo4j/driver/internal/async/inbound/InboundMessageHandler.java index 4307d01db2..b10e1a8f25 100644 --- a/driver/src/main/java/org/neo4j/driver/internal/async/inbound/InboundMessageHandler.java +++ b/driver/src/main/java/org/neo4j/driver/internal/async/inbound/InboundMessageHandler.java @@ -23,32 +23,38 @@ import static org.neo4j.driver.internal.async.connection.ChannelAttributes.messageDispatcher; import io.netty.buffer.ByteBuf; +import io.netty.channel.Channel; import io.netty.channel.ChannelHandlerContext; import io.netty.channel.SimpleChannelInboundHandler; import io.netty.handler.codec.DecoderException; +import java.util.Set; import org.neo4j.driver.Logger; import org.neo4j.driver.Logging; import org.neo4j.driver.internal.logging.ChannelActivityLogger; +import org.neo4j.driver.internal.messaging.BoltPatchesListener; import org.neo4j.driver.internal.messaging.MessageFormat; -public class InboundMessageHandler extends SimpleChannelInboundHandler { +public class InboundMessageHandler extends SimpleChannelInboundHandler implements BoltPatchesListener { private final ByteBufInput input; - private final MessageFormat.Reader reader; + private final MessageFormat messageFormat; private final Logging logging; private InboundMessageDispatcher messageDispatcher; + private MessageFormat.Reader reader; private Logger log; public InboundMessageHandler(MessageFormat messageFormat, Logging logging) { this.input = new ByteBufInput(); - this.reader = messageFormat.newReader(input); + this.messageFormat = messageFormat; this.logging = logging; + this.reader = messageFormat.newReader(input); } @Override public void handlerAdded(ChannelHandlerContext ctx) { - messageDispatcher = requireNonNull(messageDispatcher(ctx.channel())); - log = new ChannelActivityLogger(ctx.channel(), logging, getClass()); + Channel channel = ctx.channel(); + messageDispatcher = requireNonNull(messageDispatcher(channel)); + log = new ChannelActivityLogger(channel, logging, getClass()); } @Override @@ -79,4 +85,12 @@ protected void channelRead0(ChannelHandlerContext ctx, ByteBuf msg) { input.stop(); } } + + @Override + public void handle(Set patches) { + if (patches.contains(DATE_TIME_UTC_PATCH)) { + messageFormat.enableDateTimeUtc(); + reader = messageFormat.newReader(input); + } + } } diff --git a/driver/src/main/java/org/neo4j/driver/internal/async/outbound/OutboundMessageHandler.java b/driver/src/main/java/org/neo4j/driver/internal/async/outbound/OutboundMessageHandler.java index fb73c1a24c..d29843f7fe 100644 --- a/driver/src/main/java/org/neo4j/driver/internal/async/outbound/OutboundMessageHandler.java +++ b/driver/src/main/java/org/neo4j/driver/internal/async/outbound/OutboundMessageHandler.java @@ -25,25 +25,29 @@ import io.netty.handler.codec.EncoderException; import io.netty.handler.codec.MessageToMessageEncoder; import java.util.List; +import java.util.Set; import org.neo4j.driver.Logger; import org.neo4j.driver.Logging; import org.neo4j.driver.internal.async.connection.BoltProtocolUtil; import org.neo4j.driver.internal.logging.ChannelActivityLogger; +import org.neo4j.driver.internal.messaging.BoltPatchesListener; import org.neo4j.driver.internal.messaging.Message; import org.neo4j.driver.internal.messaging.MessageFormat; -public class OutboundMessageHandler extends MessageToMessageEncoder { +public class OutboundMessageHandler extends MessageToMessageEncoder implements BoltPatchesListener { public static final String NAME = OutboundMessageHandler.class.getSimpleName(); private final ChunkAwareByteBufOutput output; - private final MessageFormat.Writer writer; + private final MessageFormat messageFormat; private final Logging logging; + private MessageFormat.Writer writer; private Logger log; public OutboundMessageHandler(MessageFormat messageFormat, Logging logging) { this.output = new ChunkAwareByteBufOutput(); - this.writer = messageFormat.newWriter(output); + this.messageFormat = messageFormat; this.logging = logging; + this.writer = messageFormat.newWriter(output); } @Override @@ -79,4 +83,12 @@ protected void encode(ChannelHandlerContext ctx, Message msg, List out) BoltProtocolUtil.writeMessageBoundary(messageBuf); out.add(messageBuf); } + + @Override + public void handle(Set patches) { + if (patches.contains(DATE_TIME_UTC_PATCH)) { + messageFormat.enableDateTimeUtc(); + writer = messageFormat.newWriter(output); + } + } } diff --git a/driver/src/main/java/org/neo4j/driver/internal/handlers/HelloResponseHandler.java b/driver/src/main/java/org/neo4j/driver/internal/handlers/HelloResponseHandler.java index faaf30c8df..210b49d14f 100644 --- a/driver/src/main/java/org/neo4j/driver/internal/handlers/HelloResponseHandler.java +++ b/driver/src/main/java/org/neo4j/driver/internal/handlers/HelloResponseHandler.java @@ -18,17 +18,24 @@ */ package org.neo4j.driver.internal.handlers; +import static org.neo4j.driver.internal.async.connection.ChannelAttributes.boltPatchesListeners; +import static org.neo4j.driver.internal.async.connection.ChannelAttributes.protocolVersion; import static org.neo4j.driver.internal.async.connection.ChannelAttributes.setConnectionId; import static org.neo4j.driver.internal.async.connection.ChannelAttributes.setConnectionReadTimeout; import static org.neo4j.driver.internal.async.connection.ChannelAttributes.setServerAgent; +import static org.neo4j.driver.internal.util.MetadataExtractor.extractBoltPatches; import static org.neo4j.driver.internal.util.MetadataExtractor.extractServer; import io.netty.channel.Channel; import io.netty.channel.ChannelPromise; import java.util.Map; import java.util.Optional; +import java.util.Set; import java.util.function.Supplier; import org.neo4j.driver.Value; +import org.neo4j.driver.internal.messaging.BoltProtocolVersion; +import org.neo4j.driver.internal.messaging.v43.BoltProtocolV43; +import org.neo4j.driver.internal.messaging.v44.BoltProtocolV44; import org.neo4j.driver.internal.spi.ResponseHandler; public class HelloResponseHandler implements ResponseHandler { @@ -55,6 +62,14 @@ public void onSuccess(Map metadata) { processConfigurationHints(metadata); + BoltProtocolVersion protocolVersion = protocolVersion(channel); + if (BoltProtocolV44.VERSION.equals(protocolVersion) || BoltProtocolV43.VERSION.equals(protocolVersion)) { + Set boltPatches = extractBoltPatches(metadata); + if (!boltPatches.isEmpty()) { + boltPatchesListeners(channel).forEach(listener -> listener.handle(boltPatches)); + } + } + connectionInitializedPromise.setSuccess(); } catch (Throwable error) { onFailure(error); diff --git a/driver/src/main/java/org/neo4j/driver/internal/messaging/BoltPatchesListener.java b/driver/src/main/java/org/neo4j/driver/internal/messaging/BoltPatchesListener.java new file mode 100644 index 0000000000..24bba58e32 --- /dev/null +++ b/driver/src/main/java/org/neo4j/driver/internal/messaging/BoltPatchesListener.java @@ -0,0 +1,27 @@ +/* + * Copyright (c) "Neo4j" + * Neo4j Sweden AB [http://neo4j.com] + * + * This file is part of Neo4j. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.neo4j.driver.internal.messaging; + +import java.util.Set; + +public interface BoltPatchesListener { + String DATE_TIME_UTC_PATCH = "utc"; + + void handle(Set patches); +} diff --git a/driver/src/main/java/org/neo4j/driver/internal/messaging/MessageFormat.java b/driver/src/main/java/org/neo4j/driver/internal/messaging/MessageFormat.java index 8f04336256..a02f1ba652 100644 --- a/driver/src/main/java/org/neo4j/driver/internal/messaging/MessageFormat.java +++ b/driver/src/main/java/org/neo4j/driver/internal/messaging/MessageFormat.java @@ -34,4 +34,11 @@ interface Reader { Writer newWriter(PackOutput output); Reader newReader(PackInput input); + + /** + * Enables datetime in UTC if supported by the given message format. This is only for use with formats that support multiple modes. + *

+ * This only takes effect on subsequent writer and reader creation via {@link #newWriter(PackOutput)} and {@link #newReader(PackInput)}. + */ + default void enableDateTimeUtc() {} } diff --git a/driver/src/main/java/org/neo4j/driver/internal/messaging/common/CommonMessageReader.java b/driver/src/main/java/org/neo4j/driver/internal/messaging/common/CommonMessageReader.java index c442c2687b..0482c28d6c 100644 --- a/driver/src/main/java/org/neo4j/driver/internal/messaging/common/CommonMessageReader.java +++ b/driver/src/main/java/org/neo4j/driver/internal/messaging/common/CommonMessageReader.java @@ -33,8 +33,8 @@ public class CommonMessageReader implements MessageFormat.Reader { private final ValueUnpacker unpacker; - public CommonMessageReader(PackInput input) { - this(new CommonValueUnpacker(input)); + public CommonMessageReader(PackInput input, boolean dateTimeUtcEnabled) { + this(new CommonValueUnpacker(input, dateTimeUtcEnabled)); } protected CommonMessageReader(ValueUnpacker unpacker) { diff --git a/driver/src/main/java/org/neo4j/driver/internal/messaging/common/CommonValuePacker.java b/driver/src/main/java/org/neo4j/driver/internal/messaging/common/CommonValuePacker.java index 4e14166ab9..aac8a0d1a0 100644 --- a/driver/src/main/java/org/neo4j/driver/internal/messaging/common/CommonValuePacker.java +++ b/driver/src/main/java/org/neo4j/driver/internal/messaging/common/CommonValuePacker.java @@ -21,6 +21,7 @@ import static java.time.ZoneOffset.UTC; import java.io.IOException; +import java.time.Instant; import java.time.LocalDate; import java.time.LocalDateTime; import java.time.LocalTime; @@ -54,7 +55,9 @@ public class CommonValuePacker implements ValuePacker { public static final int LOCAL_DATE_TIME_STRUCT_SIZE = 2; public static final byte DATE_TIME_WITH_ZONE_OFFSET = 'F'; + public static final byte DATE_TIME_WITH_ZONE_OFFSET_UTC = 'I'; public static final byte DATE_TIME_WITH_ZONE_ID = 'f'; + public static final byte DATE_TIME_WITH_ZONE_ID_UTC = 'i'; public static final int DATE_TIME_STRUCT_SIZE = 3; public static final byte DURATION = 'E'; @@ -66,9 +69,11 @@ public class CommonValuePacker implements ValuePacker { public static final byte POINT_3D_STRUCT_TYPE = 'Y'; public static final int POINT_3D_STRUCT_SIZE = 4; + private final boolean dateTimeUtcEnabled; protected final PackStream.Packer packer; - public CommonValuePacker(PackOutput output) { + public CommonValuePacker(PackOutput output, boolean dateTimeUtcEnabled) { + this.dateTimeUtcEnabled = dateTimeUtcEnabled; this.packer = new PackStream.Packer(output); } @@ -119,7 +124,11 @@ protected void packInternalValue(InternalValue value) throws IOException { packLocalDateTime(value.asLocalDateTime()); break; case DATE_TIME: - packZonedDateTime(value.asZonedDateTime()); + if (dateTimeUtcEnabled) { + packZonedDateTimeUsingUtcBaseline(value.asZonedDateTime()); + } else { + packZonedDateTime(value.asZonedDateTime()); + } break; case DURATION: packDuration(value.asIsoDuration()); @@ -199,6 +208,29 @@ private void packLocalDateTime(LocalDateTime localDateTime) throws IOException { packer.pack(nano); } + private void packZonedDateTimeUsingUtcBaseline(ZonedDateTime zonedDateTime) throws IOException { + Instant instant = zonedDateTime.toInstant(); + long epochSecondLocal = instant.getEpochSecond(); + int nano = zonedDateTime.getNano(); + ZoneId zone = zonedDateTime.getZone(); + + if (zone instanceof ZoneOffset) { + int offsetSeconds = ((ZoneOffset) zone).getTotalSeconds(); + + packer.packStructHeader(DATE_TIME_STRUCT_SIZE, DATE_TIME_WITH_ZONE_OFFSET_UTC); + packer.pack(epochSecondLocal); + packer.pack(nano); + packer.pack(offsetSeconds); + } else { + String zoneId = zone.getId(); + + packer.packStructHeader(DATE_TIME_STRUCT_SIZE, DATE_TIME_WITH_ZONE_ID_UTC); + packer.pack(epochSecondLocal); + packer.pack(nano); + packer.pack(zoneId); + } + } + private void packZonedDateTime(ZonedDateTime zonedDateTime) throws IOException { long epochSecondLocal = zonedDateTime.toLocalDateTime().toEpochSecond(UTC); int nano = zonedDateTime.getNano(); diff --git a/driver/src/main/java/org/neo4j/driver/internal/messaging/common/CommonValueUnpacker.java b/driver/src/main/java/org/neo4j/driver/internal/messaging/common/CommonValueUnpacker.java index 4a722830b6..b2c1eff61a 100644 --- a/driver/src/main/java/org/neo4j/driver/internal/messaging/common/CommonValueUnpacker.java +++ b/driver/src/main/java/org/neo4j/driver/internal/messaging/common/CommonValueUnpacker.java @@ -72,7 +72,9 @@ public class CommonValueUnpacker implements ValueUnpacker { public static final int LOCAL_DATE_TIME_STRUCT_SIZE = 2; public static final byte DATE_TIME_WITH_ZONE_OFFSET = 'F'; + public static final byte DATE_TIME_WITH_ZONE_OFFSET_UTC = 'I'; public static final byte DATE_TIME_WITH_ZONE_ID = 'f'; + public static final byte DATE_TIME_WITH_ZONE_ID_UTC = 'i'; public static final int DATE_TIME_STRUCT_SIZE = 3; public static final byte DURATION = 'E'; @@ -92,9 +94,11 @@ public class CommonValueUnpacker implements ValueUnpacker { private static final int NODE_FIELDS = 3; private static final int RELATIONSHIP_FIELDS = 5; + private final boolean dateTimeUtcEnabled; protected final PackStream.Unpacker unpacker; - public CommonValueUnpacker(PackInput input) { + public CommonValueUnpacker(PackInput input, boolean dateTimeUtcEnabled) { + this.dateTimeUtcEnabled = dateTimeUtcEnabled; this.unpacker = new PackStream.Unpacker(input); } @@ -182,11 +186,25 @@ private Value unpackStruct(long size, byte type) throws IOException { 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(); + if (!dateTimeUtcEnabled) { + ensureCorrectStructSize(TypeConstructor.DATE_TIME, DATE_TIME_STRUCT_SIZE, size); + return unpackDateTimeWithZoneOffset(); + } + case DATE_TIME_WITH_ZONE_OFFSET_UTC: + if (dateTimeUtcEnabled) { + ensureCorrectStructSize(TypeConstructor.DATE_TIME, DATE_TIME_STRUCT_SIZE, size); + return unpackDateTimeUtcWithZoneOffset(); + } case DATE_TIME_WITH_ZONE_ID: - ensureCorrectStructSize(TypeConstructor.DATE_TIME, DATE_TIME_STRUCT_SIZE, size); - return unpackDateTimeWithZoneId(); + if (!dateTimeUtcEnabled) { + ensureCorrectStructSize(TypeConstructor.DATE_TIME, DATE_TIME_STRUCT_SIZE, size); + return unpackDateTimeWithZoneId(); + } + case DATE_TIME_WITH_ZONE_ID_UTC: + if (dateTimeUtcEnabled) { + ensureCorrectStructSize(TypeConstructor.DATE_TIME, DATE_TIME_STRUCT_SIZE, size); + return unpackDateTimeUtcWithZoneId(); + } case DURATION: ensureCorrectStructSize(TypeConstructor.DURATION, DURATION_TIME_STRUCT_SIZE, size); return unpackDuration(); @@ -355,6 +373,14 @@ private Value unpackDateTimeWithZoneOffset() throws IOException { return value(newZonedDateTime(epochSecondLocal, nano, ZoneOffset.ofTotalSeconds(offsetSeconds))); } + private Value unpackDateTimeUtcWithZoneOffset() throws IOException { + long epochSecondLocal = unpacker.unpackLong(); + int nano = Math.toIntExact(unpacker.unpackLong()); + int offsetSeconds = Math.toIntExact(unpacker.unpackLong()); + ZoneOffset offset = ZoneOffset.ofTotalSeconds(offsetSeconds); + return value(newZonedDateTimeUsingUtcBaseline(epochSecondLocal, nano, offset)); + } + private Value unpackDateTimeWithZoneId() throws IOException { long epochSecondLocal = unpacker.unpackLong(); int nano = Math.toIntExact(unpacker.unpackLong()); @@ -362,6 +388,14 @@ private Value unpackDateTimeWithZoneId() throws IOException { return value(newZonedDateTime(epochSecondLocal, nano, ZoneId.of(zoneIdString))); } + private Value unpackDateTimeUtcWithZoneId() throws IOException { + long epochSecondLocal = unpacker.unpackLong(); + int nano = Math.toIntExact(unpacker.unpackLong()); + String zoneIdString = unpacker.unpackString(); + ZoneId zoneId = ZoneId.of(zoneIdString); + return value(newZonedDateTimeUsingUtcBaseline(epochSecondLocal, nano, zoneId)); + } + private Value unpackDuration() throws IOException { long months = unpacker.unpackLong(); long days = unpacker.unpackLong(); @@ -391,6 +425,12 @@ private static ZonedDateTime newZonedDateTime(long epochSecondLocal, long nano, return ZonedDateTime.of(localDateTime, zoneId); } + private ZonedDateTime newZonedDateTimeUsingUtcBaseline(long epochSecondLocal, int nano, ZoneId zoneId) { + Instant instant = Instant.ofEpochSecond(epochSecondLocal, nano); + LocalDateTime localDateTime = LocalDateTime.ofInstant(instant, zoneId); + return ZonedDateTime.of(localDateTime, zoneId); + } + protected int getNodeFields() { return NODE_FIELDS; } diff --git a/driver/src/main/java/org/neo4j/driver/internal/messaging/request/HelloMessage.java b/driver/src/main/java/org/neo4j/driver/internal/messaging/request/HelloMessage.java index 95a6d7168c..4d0abe8859 100644 --- a/driver/src/main/java/org/neo4j/driver/internal/messaging/request/HelloMessage.java +++ b/driver/src/main/java/org/neo4j/driver/internal/messaging/request/HelloMessage.java @@ -21,6 +21,7 @@ import static org.neo4j.driver.Values.value; import static org.neo4j.driver.internal.security.InternalAuthToken.CREDENTIALS_KEY; +import java.util.Collections; import java.util.HashMap; import java.util.Map; import java.util.Objects; @@ -31,9 +32,16 @@ public class HelloMessage extends MessageWithMetadata { private static final String USER_AGENT_METADATA_KEY = "user_agent"; private static final String ROUTING_CONTEXT_METADATA_KEY = "routing"; + private static final String PATCH_BOLT_METADATA_KEY = "patch_bolt"; - public HelloMessage(String userAgent, Map authToken, Map routingContext) { - super(buildMetadata(userAgent, authToken, routingContext)); + private static final String DATE_TIME_UTC_PATCH_VALUE = "utc"; + + public HelloMessage( + String userAgent, + Map authToken, + Map routingContext, + boolean includeDateTimeUtc) { + super(buildMetadata(userAgent, authToken, routingContext, includeDateTimeUtc)); } @Override @@ -66,12 +74,18 @@ public String toString() { } private static Map buildMetadata( - String userAgent, Map authToken, Map routingContext) { + String userAgent, + Map authToken, + Map routingContext, + boolean includeDateTimeUtc) { Map result = new HashMap<>(authToken); result.put(USER_AGENT_METADATA_KEY, value(userAgent)); if (routingContext != null) { result.put(ROUTING_CONTEXT_METADATA_KEY, value(routingContext)); } + if (includeDateTimeUtc) { + result.put(PATCH_BOLT_METADATA_KEY, value(Collections.singleton(DATE_TIME_UTC_PATCH_VALUE))); + } return result; } } diff --git a/driver/src/main/java/org/neo4j/driver/internal/messaging/v3/BoltProtocolV3.java b/driver/src/main/java/org/neo4j/driver/internal/messaging/v3/BoltProtocolV3.java index 450730aa19..cee952d8db 100644 --- a/driver/src/main/java/org/neo4j/driver/internal/messaging/v3/BoltProtocolV3.java +++ b/driver/src/main/java/org/neo4j/driver/internal/messaging/v3/BoltProtocolV3.java @@ -83,9 +83,14 @@ public void initializeChannel( HelloMessage message; if (routingContext.isServerRoutingEnabled()) { - message = new HelloMessage(userAgent, ((InternalAuthToken) authToken).toMap(), routingContext.toMap()); + message = new HelloMessage( + userAgent, + ((InternalAuthToken) authToken).toMap(), + routingContext.toMap(), + includeDateTimeUtcPatchInHello()); } else { - message = new HelloMessage(userAgent, ((InternalAuthToken) authToken).toMap(), null); + message = new HelloMessage( + userAgent, ((InternalAuthToken) authToken).toMap(), null, includeDateTimeUtcPatchInHello()); } HelloResponseHandler handler = new HelloResponseHandler(channelInitializedPromise); @@ -183,4 +188,8 @@ protected void verifyDatabaseNameBeforeTransaction(DatabaseName databaseName) { public BoltProtocolVersion version() { return VERSION; } + + protected boolean includeDateTimeUtcPatchInHello() { + return false; + } } diff --git a/driver/src/main/java/org/neo4j/driver/internal/messaging/v3/MessageFormatV3.java b/driver/src/main/java/org/neo4j/driver/internal/messaging/v3/MessageFormatV3.java index 6471e1cb19..faad5f5309 100644 --- a/driver/src/main/java/org/neo4j/driver/internal/messaging/v3/MessageFormatV3.java +++ b/driver/src/main/java/org/neo4j/driver/internal/messaging/v3/MessageFormatV3.java @@ -31,6 +31,6 @@ public Writer newWriter(PackOutput output) { @Override public Reader newReader(PackInput input) { - return new CommonMessageReader(input); + return new CommonMessageReader(input, false); } } diff --git a/driver/src/main/java/org/neo4j/driver/internal/messaging/v3/MessageWriterV3.java b/driver/src/main/java/org/neo4j/driver/internal/messaging/v3/MessageWriterV3.java index 1e9e577f0f..977c92a997 100644 --- a/driver/src/main/java/org/neo4j/driver/internal/messaging/v3/MessageWriterV3.java +++ b/driver/src/main/java/org/neo4j/driver/internal/messaging/v3/MessageWriterV3.java @@ -45,7 +45,7 @@ public class MessageWriterV3 extends AbstractMessageWriter { public MessageWriterV3(PackOutput output) { - super(new CommonValuePacker(output), buildEncoders()); + super(new CommonValuePacker(output, false), buildEncoders()); } private static Map buildEncoders() { diff --git a/driver/src/main/java/org/neo4j/driver/internal/messaging/v4/MessageFormatV4.java b/driver/src/main/java/org/neo4j/driver/internal/messaging/v4/MessageFormatV4.java index 117b81dc31..6e75e6ae65 100644 --- a/driver/src/main/java/org/neo4j/driver/internal/messaging/v4/MessageFormatV4.java +++ b/driver/src/main/java/org/neo4j/driver/internal/messaging/v4/MessageFormatV4.java @@ -31,6 +31,6 @@ public Writer newWriter(PackOutput output) { @Override public Reader newReader(PackInput input) { - return new CommonMessageReader(input); + return new CommonMessageReader(input, false); } } diff --git a/driver/src/main/java/org/neo4j/driver/internal/messaging/v4/MessageWriterV4.java b/driver/src/main/java/org/neo4j/driver/internal/messaging/v4/MessageWriterV4.java index 429d09583a..06762171bb 100644 --- a/driver/src/main/java/org/neo4j/driver/internal/messaging/v4/MessageWriterV4.java +++ b/driver/src/main/java/org/neo4j/driver/internal/messaging/v4/MessageWriterV4.java @@ -45,7 +45,7 @@ public class MessageWriterV4 extends AbstractMessageWriter { public MessageWriterV4(PackOutput output) { - super(new CommonValuePacker(output), buildEncoders()); + super(new CommonValuePacker(output, false), buildEncoders()); } private static Map buildEncoders() { diff --git a/driver/src/main/java/org/neo4j/driver/internal/messaging/v43/BoltProtocolV43.java b/driver/src/main/java/org/neo4j/driver/internal/messaging/v43/BoltProtocolV43.java index 85623c8713..b2f31bd88a 100644 --- a/driver/src/main/java/org/neo4j/driver/internal/messaging/v43/BoltProtocolV43.java +++ b/driver/src/main/java/org/neo4j/driver/internal/messaging/v43/BoltProtocolV43.java @@ -41,4 +41,9 @@ public MessageFormat createMessageFormat() { public BoltProtocolVersion version() { return VERSION; } + + @Override + protected boolean includeDateTimeUtcPatchInHello() { + return true; + } } diff --git a/driver/src/main/java/org/neo4j/driver/internal/messaging/v43/MessageFormatV43.java b/driver/src/main/java/org/neo4j/driver/internal/messaging/v43/MessageFormatV43.java index 6424e760b9..ec3619f087 100644 --- a/driver/src/main/java/org/neo4j/driver/internal/messaging/v43/MessageFormatV43.java +++ b/driver/src/main/java/org/neo4j/driver/internal/messaging/v43/MessageFormatV43.java @@ -27,13 +27,20 @@ * Bolt message format v4.3 */ public class MessageFormatV43 implements MessageFormat { + private boolean dateTimeUtcEnabled; + @Override public Writer newWriter(PackOutput output) { - return new MessageWriterV43(output); + return new MessageWriterV43(output, dateTimeUtcEnabled); } @Override public Reader newReader(PackInput input) { - return new CommonMessageReader(input); + return new CommonMessageReader(input, dateTimeUtcEnabled); + } + + @Override + public void enableDateTimeUtc() { + dateTimeUtcEnabled = true; } } diff --git a/driver/src/main/java/org/neo4j/driver/internal/messaging/v43/MessageWriterV43.java b/driver/src/main/java/org/neo4j/driver/internal/messaging/v43/MessageWriterV43.java index f52eee3a60..c59cdfb3b7 100644 --- a/driver/src/main/java/org/neo4j/driver/internal/messaging/v43/MessageWriterV43.java +++ b/driver/src/main/java/org/neo4j/driver/internal/messaging/v43/MessageWriterV43.java @@ -52,8 +52,8 @@ * new messages such as ROUTE */ public class MessageWriterV43 extends AbstractMessageWriter { - public MessageWriterV43(PackOutput output) { - super(new CommonValuePacker(output), buildEncoders()); + public MessageWriterV43(PackOutput output, boolean dateTimeUtcEnabled) { + super(new CommonValuePacker(output, dateTimeUtcEnabled), buildEncoders()); } private static Map buildEncoders() { diff --git a/driver/src/main/java/org/neo4j/driver/internal/messaging/v44/MessageFormatV44.java b/driver/src/main/java/org/neo4j/driver/internal/messaging/v44/MessageFormatV44.java index 0a980494e6..60d47c0bf8 100644 --- a/driver/src/main/java/org/neo4j/driver/internal/messaging/v44/MessageFormatV44.java +++ b/driver/src/main/java/org/neo4j/driver/internal/messaging/v44/MessageFormatV44.java @@ -27,13 +27,20 @@ * Bolt message format v4.4 */ public class MessageFormatV44 implements MessageFormat { + private boolean dateTimeUtcEnabled; + @Override public MessageFormat.Writer newWriter(PackOutput output) { - return new MessageWriterV44(output); + return new MessageWriterV44(output, dateTimeUtcEnabled); } @Override public MessageFormat.Reader newReader(PackInput input) { - return new CommonMessageReader(input); + return new CommonMessageReader(input, dateTimeUtcEnabled); + } + + @Override + public void enableDateTimeUtc() { + dateTimeUtcEnabled = true; } } diff --git a/driver/src/main/java/org/neo4j/driver/internal/messaging/v44/MessageWriterV44.java b/driver/src/main/java/org/neo4j/driver/internal/messaging/v44/MessageWriterV44.java index 1563323d7e..31fe5d7842 100644 --- a/driver/src/main/java/org/neo4j/driver/internal/messaging/v44/MessageWriterV44.java +++ b/driver/src/main/java/org/neo4j/driver/internal/messaging/v44/MessageWriterV44.java @@ -49,8 +49,8 @@ * Bolt message writer v4.4 */ public class MessageWriterV44 extends AbstractMessageWriter { - public MessageWriterV44(PackOutput output) { - super(new CommonValuePacker(output), buildEncoders()); + public MessageWriterV44(PackOutput output, boolean dateTimeUtcEnabled) { + super(new CommonValuePacker(output, dateTimeUtcEnabled), buildEncoders()); } private static Map buildEncoders() { diff --git a/driver/src/main/java/org/neo4j/driver/internal/messaging/v5/BoltProtocolV5.java b/driver/src/main/java/org/neo4j/driver/internal/messaging/v5/BoltProtocolV5.java index 57b416f62f..74ec9cda99 100644 --- a/driver/src/main/java/org/neo4j/driver/internal/messaging/v5/BoltProtocolV5.java +++ b/driver/src/main/java/org/neo4j/driver/internal/messaging/v5/BoltProtocolV5.java @@ -36,4 +36,9 @@ public MessageFormat createMessageFormat() { public BoltProtocolVersion version() { return VERSION; } + + @Override + protected boolean includeDateTimeUtcPatchInHello() { + return false; + } } diff --git a/driver/src/main/java/org/neo4j/driver/internal/messaging/v5/MessageWriterV5.java b/driver/src/main/java/org/neo4j/driver/internal/messaging/v5/MessageWriterV5.java index 6bf756a588..e3528c816b 100644 --- a/driver/src/main/java/org/neo4j/driver/internal/messaging/v5/MessageWriterV5.java +++ b/driver/src/main/java/org/neo4j/driver/internal/messaging/v5/MessageWriterV5.java @@ -47,7 +47,7 @@ public class MessageWriterV5 extends AbstractMessageWriter { public MessageWriterV5(PackOutput output) { - super(new CommonValuePacker(output), buildEncoders()); + super(new CommonValuePacker(output, true), buildEncoders()); } private static Map buildEncoders() { diff --git a/driver/src/main/java/org/neo4j/driver/internal/messaging/v5/ValueUnpackerV5.java b/driver/src/main/java/org/neo4j/driver/internal/messaging/v5/ValueUnpackerV5.java index d01349905a..fa3cd1bdd8 100644 --- a/driver/src/main/java/org/neo4j/driver/internal/messaging/v5/ValueUnpackerV5.java +++ b/driver/src/main/java/org/neo4j/driver/internal/messaging/v5/ValueUnpackerV5.java @@ -42,7 +42,7 @@ public class ValueUnpackerV5 extends CommonValueUnpacker { private static final int RELATIONSHIP_FIELDS = 8; public ValueUnpackerV5(PackInput input) { - super(input); + super(input, true); } @Override diff --git a/driver/src/main/java/org/neo4j/driver/internal/util/MetadataExtractor.java b/driver/src/main/java/org/neo4j/driver/internal/util/MetadataExtractor.java index e320f0f008..9567d462a3 100644 --- a/driver/src/main/java/org/neo4j/driver/internal/util/MetadataExtractor.java +++ b/driver/src/main/java/org/neo4j/driver/internal/util/MetadataExtractor.java @@ -22,8 +22,10 @@ import static org.neo4j.driver.internal.types.InternalTypeSystem.TYPE_SYSTEM; import java.util.Collections; +import java.util.HashSet; import java.util.List; import java.util.Map; +import java.util.Set; import java.util.function.Function; import org.neo4j.driver.Bookmark; import org.neo4j.driver.Query; @@ -204,4 +206,13 @@ private static long extractResultConsumedAfter(Map metadata, Stri } return -1; } + + public static Set extractBoltPatches(Map metadata) { + Value boltPatch = metadata.get("patch_bolt"); + if (boltPatch != null && !boltPatch.isNull()) { + return new HashSet<>(boltPatch.asList(Value::asString)); + } else { + return Collections.emptySet(); + } + } } diff --git a/driver/src/test/java/org/neo4j/driver/internal/async/connection/HandshakeCompletedListenerTest.java b/driver/src/test/java/org/neo4j/driver/internal/async/connection/HandshakeCompletedListenerTest.java index 2ed8bbe606..369c18ed84 100644 --- a/driver/src/test/java/org/neo4j/driver/internal/async/connection/HandshakeCompletedListenerTest.java +++ b/driver/src/test/java/org/neo4j/driver/internal/async/connection/HandshakeCompletedListenerTest.java @@ -75,7 +75,7 @@ void shouldFailConnectionInitializedPromiseWhenHandshakeFails() { void shouldWriteInitializationMessageInBoltV3WhenHandshakeCompleted() { testWritingOfInitializationMessage( BoltProtocolV3.VERSION, - new HelloMessage(USER_AGENT, authToken().toMap(), Collections.emptyMap()), + new HelloMessage(USER_AGENT, authToken().toMap(), Collections.emptyMap(), false), HelloResponseHandler.class); } diff --git a/driver/src/test/java/org/neo4j/driver/internal/messaging/encode/HelloMessageEncoderTest.java b/driver/src/test/java/org/neo4j/driver/internal/messaging/encode/HelloMessageEncoderTest.java index 1577c593a3..2651f62e5b 100644 --- a/driver/src/test/java/org/neo4j/driver/internal/messaging/encode/HelloMessageEncoderTest.java +++ b/driver/src/test/java/org/neo4j/driver/internal/messaging/encode/HelloMessageEncoderTest.java @@ -42,7 +42,7 @@ void shouldEncodeHelloMessage() throws Exception { authToken.put("username", value("bob")); authToken.put("password", value("secret")); - encoder.encode(new HelloMessage("MyDriver", authToken, null), packer); + encoder.encode(new HelloMessage("MyDriver", authToken, null, false), packer); InOrder order = inOrder(packer); order.verify(packer).packStructHeader(1, HelloMessage.SIGNATURE); @@ -61,7 +61,7 @@ void shouldEncodeHelloMessageWithRoutingContext() throws Exception { Map routingContext = new HashMap<>(); routingContext.put("policy", "eu-fast"); - encoder.encode(new HelloMessage("MyDriver", authToken, routingContext), packer); + encoder.encode(new HelloMessage("MyDriver", authToken, routingContext, false), packer); InOrder order = inOrder(packer); order.verify(packer).packStructHeader(1, HelloMessage.SIGNATURE); diff --git a/driver/src/test/java/org/neo4j/driver/internal/messaging/request/HelloMessageTest.java b/driver/src/test/java/org/neo4j/driver/internal/messaging/request/HelloMessageTest.java index 3c65657e2e..bfb1dae179 100644 --- a/driver/src/test/java/org/neo4j/driver/internal/messaging/request/HelloMessageTest.java +++ b/driver/src/test/java/org/neo4j/driver/internal/messaging/request/HelloMessageTest.java @@ -39,7 +39,7 @@ void shouldHaveCorrectMetadata() { authToken.put("user", value("Alice")); authToken.put("credentials", value("SecretPassword")); - HelloMessage message = new HelloMessage("MyDriver/1.0.2", authToken, Collections.emptyMap()); + HelloMessage message = new HelloMessage("MyDriver/1.0.2", authToken, Collections.emptyMap(), false); Map expectedMetadata = new HashMap<>(authToken); expectedMetadata.put("user_agent", value("MyDriver/1.0.2")); @@ -57,7 +57,7 @@ void shouldHaveCorrectRoutingContext() { routingContext.put("region", "China"); routingContext.put("speed", "Slow"); - HelloMessage message = new HelloMessage("MyDriver/1.0.2", authToken, routingContext); + HelloMessage message = new HelloMessage("MyDriver/1.0.2", authToken, routingContext, false); Map expectedMetadata = new HashMap<>(authToken); expectedMetadata.put("user_agent", value("MyDriver/1.0.2")); @@ -71,7 +71,7 @@ void shouldNotExposeCredentialsInToString() { authToken.put(PRINCIPAL_KEY, value("Alice")); authToken.put(CREDENTIALS_KEY, value("SecretPassword")); - HelloMessage message = new HelloMessage("MyDriver/1.0.2", authToken, Collections.emptyMap()); + HelloMessage message = new HelloMessage("MyDriver/1.0.2", authToken, Collections.emptyMap(), false); assertThat(message.toString(), not(containsString("SecretPassword"))); } diff --git a/driver/src/test/java/org/neo4j/driver/internal/messaging/v3/MessageWriterV3Test.java b/driver/src/test/java/org/neo4j/driver/internal/messaging/v3/MessageWriterV3Test.java index 8ddd459563..9bf7d38487 100644 --- a/driver/src/test/java/org/neo4j/driver/internal/messaging/v3/MessageWriterV3Test.java +++ b/driver/src/test/java/org/neo4j/driver/internal/messaging/v3/MessageWriterV3Test.java @@ -99,7 +99,8 @@ protected Stream supportedMessages() { new HelloMessage( "MyDriver/1.2.3", ((InternalAuthToken) basic("neo4j", "neo4j")).toMap(), - Collections.emptyMap()), + Collections.emptyMap(), + false), GOODBYE, new BeginMessage( Collections.singleton(InternalBookmark.parse("neo4j:bookmark:v1:tx123")), diff --git a/driver/src/test/java/org/neo4j/driver/internal/messaging/v4/MessageWriterV4Test.java b/driver/src/test/java/org/neo4j/driver/internal/messaging/v4/MessageWriterV4Test.java index a368ba4433..e1bfc7d183 100644 --- a/driver/src/test/java/org/neo4j/driver/internal/messaging/v4/MessageWriterV4Test.java +++ b/driver/src/test/java/org/neo4j/driver/internal/messaging/v4/MessageWriterV4Test.java @@ -107,7 +107,8 @@ protected Stream supportedMessages() { new HelloMessage( "MyDriver/1.2.3", ((InternalAuthToken) basic("neo4j", "neo4j")).toMap(), - Collections.emptyMap()), + Collections.emptyMap(), + false), GOODBYE, new BeginMessage( Collections.singleton(InternalBookmark.parse("neo4j:bookmark:v1:tx123")), diff --git a/driver/src/test/java/org/neo4j/driver/internal/messaging/v41/MessageWriterV41Test.java b/driver/src/test/java/org/neo4j/driver/internal/messaging/v41/MessageWriterV41Test.java index 35cdada4e5..293653ddaa 100644 --- a/driver/src/test/java/org/neo4j/driver/internal/messaging/v41/MessageWriterV41Test.java +++ b/driver/src/test/java/org/neo4j/driver/internal/messaging/v41/MessageWriterV41Test.java @@ -106,7 +106,8 @@ protected Stream supportedMessages() { new HelloMessage( "MyDriver/1.2.3", ((InternalAuthToken) basic("neo4j", "neo4j")).toMap(), - Collections.emptyMap()), + Collections.emptyMap(), + false), GOODBYE, new BeginMessage( Collections.singleton(InternalBookmark.parse("neo4j:bookmark:v1:tx123")), diff --git a/driver/src/test/java/org/neo4j/driver/internal/messaging/v42/MessageWriterV42Test.java b/driver/src/test/java/org/neo4j/driver/internal/messaging/v42/MessageWriterV42Test.java index f86b78b64d..17caa6637e 100644 --- a/driver/src/test/java/org/neo4j/driver/internal/messaging/v42/MessageWriterV42Test.java +++ b/driver/src/test/java/org/neo4j/driver/internal/messaging/v42/MessageWriterV42Test.java @@ -106,7 +106,8 @@ protected Stream supportedMessages() { new HelloMessage( "MyDriver/1.2.3", ((InternalAuthToken) basic("neo4j", "neo4j")).toMap(), - Collections.emptyMap()), + Collections.emptyMap(), + false), GOODBYE, new BeginMessage( Collections.singleton(InternalBookmark.parse("neo4j:bookmark:v1:tx123")), diff --git a/driver/src/test/java/org/neo4j/driver/internal/messaging/v43/MessageWriterV43Test.java b/driver/src/test/java/org/neo4j/driver/internal/messaging/v43/MessageWriterV43Test.java index 53c4b3e95d..0fcd07461a 100644 --- a/driver/src/test/java/org/neo4j/driver/internal/messaging/v43/MessageWriterV43Test.java +++ b/driver/src/test/java/org/neo4j/driver/internal/messaging/v43/MessageWriterV43Test.java @@ -111,7 +111,8 @@ protected Stream supportedMessages() { new HelloMessage( "MyDriver/1.2.3", ((InternalAuthToken) basic("neo4j", "neo4j")).toMap(), - Collections.emptyMap()), + Collections.emptyMap(), + false), GOODBYE, new BeginMessage( Collections.singleton(InternalBookmark.parse("neo4j:bookmark:v1:tx123")), diff --git a/driver/src/test/java/org/neo4j/driver/internal/messaging/v44/MessageWriterV44Test.java b/driver/src/test/java/org/neo4j/driver/internal/messaging/v44/MessageWriterV44Test.java index 38aa432f1f..23f9345d9c 100644 --- a/driver/src/test/java/org/neo4j/driver/internal/messaging/v44/MessageWriterV44Test.java +++ b/driver/src/test/java/org/neo4j/driver/internal/messaging/v44/MessageWriterV44Test.java @@ -111,7 +111,8 @@ protected Stream supportedMessages() { new HelloMessage( "MyDriver/1.2.3", ((InternalAuthToken) basic("neo4j", "neo4j")).toMap(), - Collections.emptyMap()), + Collections.emptyMap(), + false), GOODBYE, new BeginMessage( Collections.singleton(InternalBookmark.parse("neo4j:bookmark:v1:tx123")), diff --git a/driver/src/test/java/org/neo4j/driver/internal/messaging/v5/MessageReaderV5Test.java b/driver/src/test/java/org/neo4j/driver/internal/messaging/v5/MessageReaderV5Test.java index 898625ced3..18bbcfbe68 100644 --- a/driver/src/test/java/org/neo4j/driver/internal/messaging/v5/MessageReaderV5Test.java +++ b/driver/src/test/java/org/neo4j/driver/internal/messaging/v5/MessageReaderV5Test.java @@ -113,4 +113,9 @@ private Message record(Value value) { protected boolean isElementIdEnabled() { return true; } + + @Override + protected boolean isDateTimeUtcEnabled() { + return true; + } } diff --git a/driver/src/test/java/org/neo4j/driver/internal/messaging/v5/MessageWriterV5Test.java b/driver/src/test/java/org/neo4j/driver/internal/messaging/v5/MessageWriterV5Test.java index 50529567aa..48e48b5386 100644 --- a/driver/src/test/java/org/neo4j/driver/internal/messaging/v5/MessageWriterV5Test.java +++ b/driver/src/test/java/org/neo4j/driver/internal/messaging/v5/MessageWriterV5Test.java @@ -111,7 +111,8 @@ protected Stream supportedMessages() { new HelloMessage( "MyDriver/1.2.3", ((InternalAuthToken) basic("neo4j", "neo4j")).toMap(), - Collections.emptyMap()), + Collections.emptyMap(), + false), GOODBYE, new BeginMessage( Collections.singleton(InternalBookmark.parse("neo4j:bookmark:v1:tx123")), diff --git a/driver/src/test/java/org/neo4j/driver/internal/util/messaging/AbstractMessageReaderTestBase.java b/driver/src/test/java/org/neo4j/driver/internal/util/messaging/AbstractMessageReaderTestBase.java index 59bfb63643..b80ebd95a8 100644 --- a/driver/src/test/java/org/neo4j/driver/internal/util/messaging/AbstractMessageReaderTestBase.java +++ b/driver/src/test/java/org/neo4j/driver/internal/util/messaging/AbstractMessageReaderTestBase.java @@ -97,6 +97,9 @@ private PackInput newInputWith(Message message) throws IOException { ByteBuf buffer = Unpooled.buffer(); MessageFormat messageFormat = new KnowledgeableMessageFormat(isElementIdEnabled()); + if (isDateTimeUtcEnabled()) { + messageFormat.enableDateTimeUtc(); + } MessageFormat.Writer writer = messageFormat.newWriter(new ByteBufOutput(buffer)); writer.write(message); @@ -108,4 +111,8 @@ private PackInput newInputWith(Message message) throws IOException { protected boolean isElementIdEnabled() { return false; } + + protected boolean isDateTimeUtcEnabled() { + return false; + } } diff --git a/driver/src/test/java/org/neo4j/driver/internal/util/messaging/KnowledgeableMessageFormat.java b/driver/src/test/java/org/neo4j/driver/internal/util/messaging/KnowledgeableMessageFormat.java index 1ee5cdd3e5..402a0bd409 100644 --- a/driver/src/test/java/org/neo4j/driver/internal/util/messaging/KnowledgeableMessageFormat.java +++ b/driver/src/test/java/org/neo4j/driver/internal/util/messaging/KnowledgeableMessageFormat.java @@ -50,6 +50,7 @@ */ public class KnowledgeableMessageFormat extends MessageFormatV3 { private final boolean elementIdEnabled; + private boolean dateTimeUtcEnabled; public KnowledgeableMessageFormat(boolean elementIdEnabled) { this.elementIdEnabled = elementIdEnabled; @@ -57,12 +58,17 @@ public KnowledgeableMessageFormat(boolean elementIdEnabled) { @Override public Writer newWriter(PackOutput output) { - return new KnowledgeableMessageWriter(output, elementIdEnabled); + return new KnowledgeableMessageWriter(output, elementIdEnabled, dateTimeUtcEnabled); + } + + @Override + public void enableDateTimeUtc() { + dateTimeUtcEnabled = true; } private static class KnowledgeableMessageWriter extends AbstractMessageWriter { - KnowledgeableMessageWriter(PackOutput output, boolean enableElementId) { - super(new KnowledgeableValuePacker(output, enableElementId), buildEncoders()); + KnowledgeableMessageWriter(PackOutput output, boolean enableElementId, boolean dateTimeUtcEnabled) { + super(new KnowledgeableValuePacker(output, enableElementId, dateTimeUtcEnabled), buildEncoders()); } static Map buildEncoders() { @@ -83,8 +89,8 @@ static Map buildEncoders() { private static class KnowledgeableValuePacker extends CommonValuePacker { private final boolean elementIdEnabled; - KnowledgeableValuePacker(PackOutput output, boolean elementIdEnabled) { - super(output); + KnowledgeableValuePacker(PackOutput output, boolean elementIdEnabled, boolean dateTimeUtcEnabled) { + super(output, dateTimeUtcEnabled); this.elementIdEnabled = elementIdEnabled; } diff --git a/testkit-backend/src/main/java/neo4j/org/testkit/backend/messages/TestkitModule.java b/testkit-backend/src/main/java/neo4j/org/testkit/backend/messages/TestkitModule.java index b73a068b29..399d17ec22 100644 --- a/testkit-backend/src/main/java/neo4j/org/testkit/backend/messages/TestkitModule.java +++ b/testkit-backend/src/main/java/neo4j/org/testkit/backend/messages/TestkitModule.java @@ -19,7 +19,9 @@ package neo4j.org.testkit.backend.messages; import com.fasterxml.jackson.databind.module.SimpleModule; +import java.time.ZonedDateTime; import java.util.List; +import neo4j.org.testkit.backend.messages.requests.deserializer.TestkitCypherDateTimeDeserializer; import neo4j.org.testkit.backend.messages.requests.deserializer.TestkitListDeserializer; import neo4j.org.testkit.backend.messages.responses.serializer.TestkitListValueSerializer; import neo4j.org.testkit.backend.messages.responses.serializer.TestkitMapValueSerializer; @@ -39,6 +41,7 @@ public class TestkitModule extends SimpleModule { public TestkitModule() { this.addDeserializer(List.class, new TestkitListDeserializer()); + this.addDeserializer(ZonedDateTime.class, new TestkitCypherDateTimeDeserializer()); this.addSerializer(Value.class, new TestkitValueSerializer()); this.addSerializer(NodeValue.class, new TestkitNodeValueSerializer()); diff --git a/testkit-backend/src/main/java/neo4j/org/testkit/backend/messages/requests/GetFeatures.java b/testkit-backend/src/main/java/neo4j/org/testkit/backend/messages/requests/GetFeatures.java index 3f3e141e19..5d00a740f7 100644 --- a/testkit-backend/src/main/java/neo4j/org/testkit/backend/messages/requests/GetFeatures.java +++ b/testkit-backend/src/main/java/neo4j/org/testkit/backend/messages/requests/GetFeatures.java @@ -56,7 +56,9 @@ public class GetFeatures implements TestkitRequest { "Feature:API:SSLConfig", "Detail:DefaultSecurityConfigValueEquality", "Detail:ThrowOnMissingId", - "Optimization:ImplicitDefaultArguments")); + "Optimization:ImplicitDefaultArguments", + "Feature:Bolt:Patch:UTC", + "Feature:API:Type.Temporal")); private static final Set SYNC_FEATURES = new HashSet<>(Arrays.asList( "Feature:Bolt:3.0", diff --git a/testkit-backend/src/main/java/neo4j/org/testkit/backend/messages/requests/StartTest.java b/testkit-backend/src/main/java/neo4j/org/testkit/backend/messages/requests/StartTest.java index 4205afb094..b68586835e 100644 --- a/testkit-backend/src/main/java/neo4j/org/testkit/backend/messages/requests/StartTest.java +++ b/testkit-backend/src/main/java/neo4j/org/testkit/backend/messages/requests/StartTest.java @@ -83,6 +83,9 @@ public class StartTest implements TestkitRequest { "^.*\\.TestOptimizations\\.test_uses_implicit_default_arguments_multi_query_nested$", skipMessage); skipMessage = "This test became flaky and needs investigation"; COMMON_SKIP_PATTERN_TO_REASON.put("^.*\\.test_trusted_ca_correct_hostname$", skipMessage); + skipMessage = "Additional type support is needed"; + COMMON_SKIP_PATTERN_TO_REASON.put( + "^neo4j\\.datatypes\\.test_temporal_types\\.TestDataTypes\\..*$", skipMessage); ASYNC_SKIP_PATTERN_TO_REASON.putAll(COMMON_SKIP_PATTERN_TO_REASON); @@ -171,6 +174,7 @@ public Mono processReactive(TestkitState testkitState) { } private TestkitResponse createResponse(Map skipPatternToReason) { + System.out.println(data.getTestName()); return skipPatternToReason.entrySet().stream() .filter(entry -> data.getTestName().matches(entry.getKey())) .findFirst() diff --git a/testkit-backend/src/main/java/neo4j/org/testkit/backend/messages/requests/deserializer/TestkitCypherDateTimeDeserializer.java b/testkit-backend/src/main/java/neo4j/org/testkit/backend/messages/requests/deserializer/TestkitCypherDateTimeDeserializer.java new file mode 100644 index 0000000000..b1d24d4b53 --- /dev/null +++ b/testkit-backend/src/main/java/neo4j/org/testkit/backend/messages/requests/deserializer/TestkitCypherDateTimeDeserializer.java @@ -0,0 +1,92 @@ +/* + * Copyright (c) "Neo4j" + * Neo4j Sweden AB [http://neo4j.com] + * + * This file is part of Neo4j. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package neo4j.org.testkit.backend.messages.requests.deserializer; + +import com.fasterxml.jackson.core.JacksonException; +import com.fasterxml.jackson.core.JsonParser; +import com.fasterxml.jackson.core.JsonToken; +import com.fasterxml.jackson.databind.DeserializationContext; +import com.fasterxml.jackson.databind.deser.std.StdDeserializer; +import java.io.IOException; +import java.lang.reflect.Field; +import java.time.ZoneId; +import java.time.ZoneOffset; +import java.time.ZonedDateTime; + +public class TestkitCypherDateTimeDeserializer extends StdDeserializer { + public TestkitCypherDateTimeDeserializer() { + super(ZonedDateTime.class); + } + + @Override + public ZonedDateTime deserialize(JsonParser p, DeserializationContext ctxt) throws IOException, JacksonException { + + CypherDateTime dateTime = new CypherDateTime(); + + JsonToken token = p.currentToken(); + while (token == JsonToken.FIELD_NAME + || token == JsonToken.VALUE_NUMBER_INT + || token == JsonToken.VALUE_STRING) { + if (token == JsonToken.VALUE_NUMBER_INT) { + String field = p.getCurrentName(); + int value = p.getValueAsInt(); + setField(dateTime, field, value); + } else if (token == JsonToken.VALUE_STRING) { + String field = p.getCurrentName(); + String value = p.getValueAsString(); + setField(dateTime, field, value); + } + token = p.nextToken(); + } + + ZoneId zoneId = dateTime.timezone_id != null + ? ZoneId.of(dateTime.timezone_id) + : ZoneOffset.ofTotalSeconds(dateTime.utc_offset_s); + return ZonedDateTime.of( + dateTime.year, + dateTime.month, + dateTime.day, + dateTime.hour, + dateTime.minute, + dateTime.second, + dateTime.nanosecond, + zoneId); + } + + private void setField(CypherDateTime dateTime, String fieldName, Object value) { + try { + Field field = CypherDateTime.class.getDeclaredField(fieldName); + field.set(dateTime, value); + } catch (NoSuchFieldException | IllegalAccessException e) { + // ignored + } + } + + private static class CypherDateTime { + Integer year; + Integer month; + Integer day; + Integer hour; + Integer minute; + Integer second; + Integer nanosecond; + Integer utc_offset_s; + String timezone_id; + } +} diff --git a/testkit-backend/src/main/java/neo4j/org/testkit/backend/messages/responses/serializer/GenUtils.java b/testkit-backend/src/main/java/neo4j/org/testkit/backend/messages/responses/serializer/GenUtils.java index 8e506f7fdc..c783c3efa0 100644 --- a/testkit-backend/src/main/java/neo4j/org/testkit/backend/messages/responses/serializer/GenUtils.java +++ b/testkit-backend/src/main/java/neo4j/org/testkit/backend/messages/responses/serializer/GenUtils.java @@ -20,6 +20,7 @@ import com.fasterxml.jackson.core.JsonGenerator; import java.io.IOException; +import java.time.ZonedDateTime; import java.util.List; import java.util.Map; import lombok.AccessLevel; @@ -68,6 +69,8 @@ public static Class cypherTypeToJavaType(String typeString) { return List.class; case "CypherMap": return Map.class; + case "CypherDateTime": + return ZonedDateTime.class; case "CypherNull": return null; default: