diff --git a/driver/src/main/java/org/neo4j/driver/internal/async/connection/BoltProtocolUtil.java b/driver/src/main/java/org/neo4j/driver/internal/async/connection/BoltProtocolUtil.java index 71eadbc56c..5576de874f 100644 --- a/driver/src/main/java/org/neo4j/driver/internal/async/connection/BoltProtocolUtil.java +++ b/driver/src/main/java/org/neo4j/driver/internal/async/connection/BoltProtocolUtil.java @@ -25,7 +25,7 @@ import org.neo4j.driver.internal.messaging.v4.BoltProtocolV4; import org.neo4j.driver.internal.messaging.v41.BoltProtocolV41; import org.neo4j.driver.internal.messaging.v42.BoltProtocolV42; -import org.neo4j.driver.internal.messaging.v43.BoltProtocolV43; +import org.neo4j.driver.internal.messaging.v44.BoltProtocolV44; import static io.netty.buffer.Unpooled.copyInt; import static io.netty.buffer.Unpooled.unreleasableBuffer; @@ -42,7 +42,7 @@ public final class BoltProtocolUtil private static final ByteBuf HANDSHAKE_BUF = unreleasableBuffer( copyInt( BOLT_MAGIC_PREAMBLE, - BoltProtocolV43.VERSION.toIntRange( BoltProtocolV42.VERSION ), + BoltProtocolV44.VERSION.toIntRange( BoltProtocolV42.VERSION ), BoltProtocolV41.VERSION.toInt(), BoltProtocolV4.VERSION.toInt(), BoltProtocolV3.VERSION.toInt() ) ).asReadOnly(); diff --git a/driver/src/main/java/org/neo4j/driver/internal/messaging/BoltProtocol.java b/driver/src/main/java/org/neo4j/driver/internal/messaging/BoltProtocol.java index 8208f86e5e..dee84f2a23 100644 --- a/driver/src/main/java/org/neo4j/driver/internal/messaging/BoltProtocol.java +++ b/driver/src/main/java/org/neo4j/driver/internal/messaging/BoltProtocol.java @@ -40,6 +40,7 @@ import org.neo4j.driver.internal.messaging.v41.BoltProtocolV41; import org.neo4j.driver.internal.messaging.v42.BoltProtocolV42; import org.neo4j.driver.internal.messaging.v43.BoltProtocolV43; +import org.neo4j.driver.internal.messaging.v44.BoltProtocolV44; import org.neo4j.driver.internal.spi.Connection; import static org.neo4j.driver.internal.async.connection.ChannelAttributes.protocolVersion; @@ -166,6 +167,10 @@ else if ( BoltProtocolV43.VERSION.equals( version ) ) { return BoltProtocolV43.INSTANCE; } + else if ( BoltProtocolV44.VERSION.equals( version ) ) + { + return BoltProtocolV44.INSTANCE; + } throw new ClientException( "Unknown protocol version: " + version ); } } diff --git a/driver/src/main/java/org/neo4j/driver/internal/messaging/v44/BoltProtocolV44.java b/driver/src/main/java/org/neo4j/driver/internal/messaging/v44/BoltProtocolV44.java new file mode 100644 index 0000000000..735c2676db --- /dev/null +++ b/driver/src/main/java/org/neo4j/driver/internal/messaging/v44/BoltProtocolV44.java @@ -0,0 +1,45 @@ +/* + * 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.v44; + +import org.neo4j.driver.internal.messaging.BoltProtocol; +import org.neo4j.driver.internal.messaging.BoltProtocolVersion; +import org.neo4j.driver.internal.messaging.MessageFormat; +import org.neo4j.driver.internal.messaging.v43.BoltProtocolV43; + +/** + * Definition of the Bolt Protocol 4.4 + */ +public class BoltProtocolV44 extends BoltProtocolV43 +{ + public static final BoltProtocolVersion VERSION = new BoltProtocolVersion( 4, 4 ); + public static final BoltProtocol INSTANCE = new BoltProtocolV44(); + + @Override + public MessageFormat createMessageFormat() + { + return new MessageFormatV44(); + } + + @Override + public BoltProtocolVersion version() + { + return VERSION; + } +} 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 new file mode 100644 index 0000000000..7bca125094 --- /dev/null +++ b/driver/src/main/java/org/neo4j/driver/internal/messaging/v44/MessageFormatV44.java @@ -0,0 +1,42 @@ +/* + * Copyright (c) "Neo4j" + * Neo4j Sweden AB [http://neo4j.com] + * + * This file is part of Neo4j. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.neo4j.driver.internal.messaging.v44; + +import org.neo4j.driver.internal.messaging.MessageFormat; +import org.neo4j.driver.internal.messaging.common.CommonMessageReader; +import org.neo4j.driver.internal.packstream.PackInput; +import org.neo4j.driver.internal.packstream.PackOutput; + +/** + * Bolt message format v4.4 + */ +public class MessageFormatV44 implements MessageFormat +{ + @Override + public MessageFormat.Writer newWriter( PackOutput output ) + { + return new MessageWriterV44( output ); + } + + @Override + public MessageFormat.Reader newReader( PackInput input ) + { + return new CommonMessageReader( input ); + } +} 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 new file mode 100644 index 0000000000..090dc4559d --- /dev/null +++ b/driver/src/main/java/org/neo4j/driver/internal/messaging/v44/MessageWriterV44.java @@ -0,0 +1,77 @@ +/* + * 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.v44; + +import java.util.Map; + +import org.neo4j.driver.internal.messaging.AbstractMessageWriter; +import org.neo4j.driver.internal.messaging.MessageEncoder; +import org.neo4j.driver.internal.messaging.common.CommonValuePacker; +import org.neo4j.driver.internal.messaging.encode.BeginMessageEncoder; +import org.neo4j.driver.internal.messaging.encode.CommitMessageEncoder; +import org.neo4j.driver.internal.messaging.encode.DiscardMessageEncoder; +import org.neo4j.driver.internal.messaging.encode.GoodbyeMessageEncoder; +import org.neo4j.driver.internal.messaging.encode.HelloMessageEncoder; +import org.neo4j.driver.internal.messaging.encode.PullMessageEncoder; +import org.neo4j.driver.internal.messaging.encode.ResetMessageEncoder; +import org.neo4j.driver.internal.messaging.encode.RollbackMessageEncoder; +import org.neo4j.driver.internal.messaging.encode.RouteMessageEncoder; +import org.neo4j.driver.internal.messaging.encode.RunWithMetadataMessageEncoder; +import org.neo4j.driver.internal.messaging.request.BeginMessage; +import org.neo4j.driver.internal.messaging.request.CommitMessage; +import org.neo4j.driver.internal.messaging.request.DiscardMessage; +import org.neo4j.driver.internal.messaging.request.GoodbyeMessage; +import org.neo4j.driver.internal.messaging.request.HelloMessage; +import org.neo4j.driver.internal.messaging.request.PullMessage; +import org.neo4j.driver.internal.messaging.request.ResetMessage; +import org.neo4j.driver.internal.messaging.request.RollbackMessage; +import org.neo4j.driver.internal.messaging.request.RouteMessage; +import org.neo4j.driver.internal.messaging.request.RunWithMetadataMessage; +import org.neo4j.driver.internal.packstream.PackOutput; +import org.neo4j.driver.internal.util.Iterables; + +/** + * Bolt message writer v4.4 + */ +public class MessageWriterV44 extends AbstractMessageWriter +{ + public MessageWriterV44( PackOutput output ) + { + super( new CommonValuePacker( output ), buildEncoders() ); + } + + private static Map buildEncoders() + { + Map result = Iterables.newHashMapWithSize( 9 ); + result.put( HelloMessage.SIGNATURE, new HelloMessageEncoder() ); + result.put( GoodbyeMessage.SIGNATURE, new GoodbyeMessageEncoder() ); + result.put( RunWithMetadataMessage.SIGNATURE, new RunWithMetadataMessageEncoder() ); + result.put( RouteMessage.SIGNATURE, new RouteMessageEncoder() ); + + result.put( DiscardMessage.SIGNATURE, new DiscardMessageEncoder() ); + result.put( PullMessage.SIGNATURE, new PullMessageEncoder() ); + + result.put( BeginMessage.SIGNATURE, new BeginMessageEncoder() ); + result.put( CommitMessage.SIGNATURE, new CommitMessageEncoder() ); + result.put( RollbackMessage.SIGNATURE, new RollbackMessageEncoder() ); + + result.put( ResetMessage.SIGNATURE, new ResetMessageEncoder() ); + return result; + } +} diff --git a/driver/src/test/java/org/neo4j/driver/internal/async/connection/BoltProtocolUtilTest.java b/driver/src/test/java/org/neo4j/driver/internal/async/connection/BoltProtocolUtilTest.java index d19a699ab4..eb4a22c00b 100644 --- a/driver/src/test/java/org/neo4j/driver/internal/async/connection/BoltProtocolUtilTest.java +++ b/driver/src/test/java/org/neo4j/driver/internal/async/connection/BoltProtocolUtilTest.java @@ -25,7 +25,7 @@ import org.neo4j.driver.internal.messaging.v3.BoltProtocolV3; import org.neo4j.driver.internal.messaging.v4.BoltProtocolV4; import org.neo4j.driver.internal.messaging.v41.BoltProtocolV41; -import org.neo4j.driver.internal.messaging.v43.BoltProtocolV43; +import org.neo4j.driver.internal.messaging.v44.BoltProtocolV44; import static org.junit.jupiter.api.Assertions.assertEquals; import static org.neo4j.driver.internal.async.connection.BoltProtocolUtil.BOLT_MAGIC_PREAMBLE; @@ -43,7 +43,7 @@ void shouldReturnHandshakeBuf() { assertByteBufContains( handshakeBuf(), - BOLT_MAGIC_PREAMBLE, (1 << 16) | BoltProtocolV43.VERSION.toInt(), BoltProtocolV41.VERSION.toInt(), + BOLT_MAGIC_PREAMBLE, (2 << 16) | BoltProtocolV44.VERSION.toInt(), BoltProtocolV41.VERSION.toInt(), BoltProtocolV4.VERSION.toInt(), BoltProtocolV3.VERSION.toInt() ); } @@ -51,7 +51,7 @@ void shouldReturnHandshakeBuf() @Test void shouldReturnHandshakeString() { - assertEquals( "[0x6060b017, 66308, 260, 4, 3]", handshakeString() ); + assertEquals( "[0x6060b017, 132100, 260, 4, 3]", handshakeString() ); } @Test diff --git a/driver/src/test/java/org/neo4j/driver/internal/messaging/v44/BoltProtocolV44Test.java b/driver/src/test/java/org/neo4j/driver/internal/messaging/v44/BoltProtocolV44Test.java new file mode 100644 index 0000000000..aa277c66ca --- /dev/null +++ b/driver/src/test/java/org/neo4j/driver/internal/messaging/v44/BoltProtocolV44Test.java @@ -0,0 +1,540 @@ +/* + * 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.v44; + +import io.netty.channel.ChannelPromise; +import io.netty.channel.embedded.EmbeddedChannel; +import org.junit.jupiter.api.AfterEach; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.params.ParameterizedTest; +import org.junit.jupiter.params.provider.EnumSource; +import org.mockito.ArgumentCaptor; + +import java.util.HashMap; +import java.util.Map; +import java.util.concurrent.CompletableFuture; +import java.util.concurrent.CompletionStage; + +import org.neo4j.driver.AccessMode; +import org.neo4j.driver.AuthTokens; +import org.neo4j.driver.Bookmark; +import org.neo4j.driver.Logging; +import org.neo4j.driver.Query; +import org.neo4j.driver.TransactionConfig; +import org.neo4j.driver.Value; +import org.neo4j.driver.internal.BookmarkHolder; +import org.neo4j.driver.internal.DatabaseName; +import org.neo4j.driver.internal.DefaultBookmarkHolder; +import org.neo4j.driver.internal.InternalBookmark; +import org.neo4j.driver.internal.async.UnmanagedTransaction; +import org.neo4j.driver.internal.async.connection.ChannelAttributes; +import org.neo4j.driver.internal.async.inbound.InboundMessageDispatcher; +import org.neo4j.driver.internal.cluster.RoutingContext; +import org.neo4j.driver.internal.cursor.AsyncResultCursor; +import org.neo4j.driver.internal.cursor.ResultCursorFactory; +import org.neo4j.driver.internal.handlers.BeginTxResponseHandler; +import org.neo4j.driver.internal.handlers.CommitTxResponseHandler; +import org.neo4j.driver.internal.handlers.PullAllResponseHandler; +import org.neo4j.driver.internal.handlers.RollbackTxResponseHandler; +import org.neo4j.driver.internal.handlers.RunResponseHandler; +import org.neo4j.driver.internal.messaging.BoltProtocol; +import org.neo4j.driver.internal.messaging.MessageFormat; +import org.neo4j.driver.internal.messaging.request.BeginMessage; +import org.neo4j.driver.internal.messaging.request.CommitMessage; +import org.neo4j.driver.internal.messaging.request.GoodbyeMessage; +import org.neo4j.driver.internal.messaging.request.HelloMessage; +import org.neo4j.driver.internal.messaging.request.PullMessage; +import org.neo4j.driver.internal.messaging.request.RollbackMessage; +import org.neo4j.driver.internal.messaging.request.RunWithMetadataMessage; +import org.neo4j.driver.internal.security.InternalAuthToken; +import org.neo4j.driver.internal.spi.Connection; +import org.neo4j.driver.internal.spi.ResponseHandler; + +import static java.time.Duration.ofSeconds; +import static java.util.Collections.emptyMap; +import static java.util.Collections.singletonMap; +import static org.hamcrest.Matchers.hasSize; +import static org.hamcrest.Matchers.instanceOf; +import static org.hamcrest.junit.MatcherAssert.assertThat; +import static org.junit.jupiter.api.Assertions.assertDoesNotThrow; +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertFalse; +import static org.junit.jupiter.api.Assertions.assertNotNull; +import static org.junit.jupiter.api.Assertions.assertNull; +import static org.junit.jupiter.api.Assertions.assertSame; +import static org.junit.jupiter.api.Assertions.assertThrows; +import static org.junit.jupiter.api.Assertions.assertTrue; +import static org.mockito.ArgumentMatchers.any; +import static org.mockito.ArgumentMatchers.eq; +import static org.mockito.Mockito.doAnswer; +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.verify; +import static org.mockito.Mockito.when; +import static org.neo4j.driver.AccessMode.WRITE; +import static org.neo4j.driver.Values.value; +import static org.neo4j.driver.internal.DatabaseNameUtil.database; +import static org.neo4j.driver.internal.DatabaseNameUtil.defaultDatabase; +import static org.neo4j.driver.internal.handlers.pulln.FetchSizeUtil.UNLIMITED_FETCH_SIZE; +import static org.neo4j.driver.util.TestUtil.anyServerVersion; +import static org.neo4j.driver.util.TestUtil.await; +import static org.neo4j.driver.util.TestUtil.connectionMock; + +public class BoltProtocolV44Test +{ + protected static final String QUERY_TEXT = "RETURN $x"; + protected static final Map PARAMS = singletonMap( "x", value( 42 ) ); + protected static final Query QUERY = new Query( QUERY_TEXT, value( PARAMS ) ); + + protected final BoltProtocol protocol = createProtocol(); + private final EmbeddedChannel channel = new EmbeddedChannel(); + private final InboundMessageDispatcher messageDispatcher = new InboundMessageDispatcher( channel, Logging.none() ); + + private final TransactionConfig txConfig = TransactionConfig.builder() + .withTimeout( ofSeconds( 12 ) ) + .withMetadata( singletonMap( "key", value( 42 ) ) ) + .build(); + + protected BoltProtocol createProtocol() + { + return BoltProtocolV44.INSTANCE; + } + + @BeforeEach + void beforeEach() + { + ChannelAttributes.setMessageDispatcher( channel, messageDispatcher ); + } + + @AfterEach + void afterEach() + { + channel.finishAndReleaseAll(); + } + + @Test + void shouldCreateMessageFormat() + { + assertThat( protocol.createMessageFormat(), instanceOf( expectedMessageFormatType() ) ); + } + + @Test + void shouldInitializeChannel() + { + ChannelPromise promise = channel.newPromise(); + + protocol.initializeChannel( "MyDriver/0.0.1", dummyAuthToken(), RoutingContext.EMPTY, promise ); + + assertThat( channel.outboundMessages(), hasSize( 1 ) ); + assertThat( channel.outboundMessages().poll(), instanceOf( HelloMessage.class ) ); + assertEquals( 1, messageDispatcher.queuedHandlersCount() ); + assertFalse( promise.isDone() ); + + Map metadata = new HashMap<>(); + metadata.put( "server", value( anyServerVersion().toString() ) ); + metadata.put( "connection_id", value( "bolt-42" ) ); + + messageDispatcher.handleSuccessMessage( metadata ); + + assertTrue( promise.isDone() ); + assertTrue( promise.isSuccess() ); + } + + @Test + void shouldPrepareToCloseChannel() + { + protocol.prepareToCloseChannel( channel ); + + assertThat( channel.outboundMessages(), hasSize( 1 ) ); + assertThat( channel.outboundMessages().poll(), instanceOf( GoodbyeMessage.class ) ); + assertEquals( 1, messageDispatcher.queuedHandlersCount() ); + } + + @Test + void shouldFailToInitializeChannelWhenErrorIsReceived() + { + ChannelPromise promise = channel.newPromise(); + + protocol.initializeChannel( "MyDriver/2.2.1", dummyAuthToken(), RoutingContext.EMPTY, promise ); + + assertThat( channel.outboundMessages(), hasSize( 1 ) ); + assertThat( channel.outboundMessages().poll(), instanceOf( HelloMessage.class ) ); + assertEquals( 1, messageDispatcher.queuedHandlersCount() ); + assertFalse( promise.isDone() ); + + messageDispatcher.handleFailureMessage( "Neo.TransientError.General.DatabaseUnavailable", "Error!" ); + + assertTrue( promise.isDone() ); + assertFalse( promise.isSuccess() ); + } + + @Test + void shouldBeginTransactionWithoutBookmark() + { + Connection connection = connectionMock( protocol ); + + CompletionStage stage = protocol.beginTransaction( connection, InternalBookmark.empty(), TransactionConfig.empty() ); + + verify( connection ) + .writeAndFlush( eq( new BeginMessage( InternalBookmark.empty(), TransactionConfig.empty(), defaultDatabase(), WRITE ) ), + any( BeginTxResponseHandler.class ) ); + assertNull( await( stage ) ); + } + + @Test + void shouldBeginTransactionWithBookmarks() + { + Connection connection = connectionMock( protocol ); + Bookmark bookmark = InternalBookmark.parse( "neo4j:bookmark:v1:tx100" ); + + CompletionStage stage = protocol.beginTransaction( connection, bookmark, TransactionConfig.empty() ); + + verify( connection ) + .writeAndFlush( eq( new BeginMessage( bookmark, TransactionConfig.empty(), defaultDatabase(), WRITE ) ), any( BeginTxResponseHandler.class ) ); + assertNull( await( stage ) ); + } + + @Test + void shouldBeginTransactionWithConfig() + { + Connection connection = connectionMock( protocol ); + + CompletionStage stage = protocol.beginTransaction( connection, InternalBookmark.empty(), txConfig ); + + verify( connection ) + .writeAndFlush( eq( new BeginMessage( InternalBookmark.empty(), txConfig, defaultDatabase(), WRITE ) ), any( BeginTxResponseHandler.class ) ); + assertNull( await( stage ) ); + } + + @Test + void shouldBeginTransactionWithBookmarksAndConfig() + { + Connection connection = connectionMock( protocol ); + Bookmark bookmark = InternalBookmark.parse( "neo4j:bookmark:v1:tx4242" ); + + CompletionStage stage = protocol.beginTransaction( connection, bookmark, txConfig ); + + verify( connection ).writeAndFlush( eq( new BeginMessage( bookmark, txConfig, defaultDatabase(), WRITE ) ), any( BeginTxResponseHandler.class ) ); + assertNull( await( stage ) ); + } + + @Test + void shouldCommitTransaction() + { + String bookmarkString = "neo4j:bookmark:v1:tx4242"; + + Connection connection = connectionMock( protocol ); + when( connection.protocol() ).thenReturn( protocol ); + doAnswer( invocation -> + { + ResponseHandler commitHandler = invocation.getArgument( 1 ); + commitHandler.onSuccess( singletonMap( "bookmark", value( bookmarkString ) ) ); + return null; + } ).when( connection ).writeAndFlush( eq( CommitMessage.COMMIT ), any() ); + + CompletionStage stage = protocol.commitTransaction( connection ); + + verify( connection ).writeAndFlush( eq( CommitMessage.COMMIT ), any( CommitTxResponseHandler.class ) ); + assertEquals( InternalBookmark.parse( bookmarkString ), await( stage ) ); + } + + @Test + void shouldRollbackTransaction() + { + Connection connection = connectionMock( protocol ); + + CompletionStage stage = protocol.rollbackTransaction( connection ); + + verify( connection ).writeAndFlush( eq( RollbackMessage.ROLLBACK ), any( RollbackTxResponseHandler.class ) ); + assertNull( await( stage ) ); + } + + @ParameterizedTest + @EnumSource( AccessMode.class ) + void shouldRunInAutoCommitTransactionAndWaitForRunResponse( AccessMode mode ) throws Exception + { + testRunAndWaitForRunResponse( true, TransactionConfig.empty(), mode ); + } + + @ParameterizedTest + @EnumSource( AccessMode.class ) + void shouldRunInAutoCommitWithConfigTransactionAndWaitForRunResponse( AccessMode mode ) throws Exception + { + testRunAndWaitForRunResponse( true, txConfig, mode ); + } + + @ParameterizedTest + @EnumSource( AccessMode.class ) + void shouldRunInAutoCommitTransactionAndWaitForSuccessRunResponse( AccessMode mode ) throws Exception + { + testSuccessfulRunInAutoCommitTxWithWaitingForResponse( InternalBookmark.empty(), TransactionConfig.empty(), mode ); + } + + @ParameterizedTest + @EnumSource( AccessMode.class ) + void shouldRunInAutoCommitTransactionWithBookmarkAndConfigAndWaitForSuccessRunResponse( AccessMode mode ) throws Exception + { + testSuccessfulRunInAutoCommitTxWithWaitingForResponse( InternalBookmark.parse( "neo4j:bookmark:v1:tx65" ), txConfig, mode ); + } + + @ParameterizedTest + @EnumSource( AccessMode.class ) + void shouldRunInAutoCommitTransactionAndWaitForFailureRunResponse( AccessMode mode ) throws Exception + { + testFailedRunInAutoCommitTxWithWaitingForResponse( InternalBookmark.empty(), TransactionConfig.empty(), mode ); + } + + @ParameterizedTest + @EnumSource( AccessMode.class ) + void shouldRunInAutoCommitTransactionWithBookmarkAndConfigAndWaitForFailureRunResponse( AccessMode mode ) throws Exception + { + testFailedRunInAutoCommitTxWithWaitingForResponse( InternalBookmark.parse( "neo4j:bookmark:v1:tx163" ), txConfig, mode ); + } + + @ParameterizedTest + @EnumSource( AccessMode.class ) + void shouldRunInUnmanagedTransactionAndWaitForRunResponse( AccessMode mode ) throws Exception + { + testRunAndWaitForRunResponse( false, TransactionConfig.empty(), mode ); + } + + @ParameterizedTest + @EnumSource( AccessMode.class ) + void shouldRunInUnmanagedTransactionAndWaitForSuccessRunResponse( AccessMode mode ) throws Exception + { + testRunInUnmanagedTransactionAndWaitForRunResponse( true, mode ); + } + + @ParameterizedTest + @EnumSource( AccessMode.class ) + void shouldRunInUnmanagedTransactionAndWaitForFailureRunResponse( AccessMode mode ) throws Exception + { + testRunInUnmanagedTransactionAndWaitForRunResponse( false, mode ); + } + + @Test + void databaseNameInBeginTransaction() + { + testDatabaseNameSupport( false ); + } + + @Test + void databaseNameForAutoCommitTransactions() + { + testDatabaseNameSupport( true ); + } + + @Test + void shouldSupportDatabaseNameInBeginTransaction() + { + CompletionStage txStage = protocol.beginTransaction( connectionMock( "foo", protocol ), InternalBookmark.empty(), TransactionConfig.empty() ); + + assertDoesNotThrow( () -> await( txStage ) ); + } + + @Test + void shouldNotSupportDatabaseNameForAutoCommitTransactions() + { + assertDoesNotThrow( + () -> protocol.runInAutoCommitTransaction( connectionMock( "foo", protocol ), + new Query( "RETURN 1" ), BookmarkHolder.NO_OP, TransactionConfig.empty(), UNLIMITED_FETCH_SIZE ) ); + } + + private Class expectedMessageFormatType() + { + return MessageFormatV44.class; + } + + private void testFailedRunInAutoCommitTxWithWaitingForResponse( Bookmark bookmark, TransactionConfig config, AccessMode mode ) throws Exception + { + // Given + Connection connection = connectionMock( mode, protocol ); + BookmarkHolder bookmarkHolder = new DefaultBookmarkHolder( bookmark ); + + CompletableFuture cursorFuture = + protocol.runInAutoCommitTransaction( connection, QUERY, bookmarkHolder, config, UNLIMITED_FETCH_SIZE ) + .asyncResult() + .toCompletableFuture(); + + ResponseHandler runHandler = verifySessionRunInvoked( connection, bookmark, config, mode, defaultDatabase() ); + assertFalse( cursorFuture.isDone() ); + + // When I response to Run message with a failure + Throwable error = new RuntimeException(); + runHandler.onFailure( error ); + + // Then + assertEquals( bookmark, bookmarkHolder.getBookmark() ); + assertTrue( cursorFuture.isDone() ); + Throwable actual = assertThrows( error.getClass(), () -> await( cursorFuture.get().mapSuccessfulRunCompletionAsync() ) ); + assertSame( error, actual ); + } + + private void testSuccessfulRunInAutoCommitTxWithWaitingForResponse( Bookmark bookmark, TransactionConfig config, AccessMode mode ) throws Exception + { + // Given + Connection connection = connectionMock( mode, protocol ); + BookmarkHolder bookmarkHolder = new DefaultBookmarkHolder( bookmark ); + + CompletableFuture cursorFuture = + protocol.runInAutoCommitTransaction( connection, QUERY, bookmarkHolder, config, UNLIMITED_FETCH_SIZE ) + .asyncResult() + .toCompletableFuture(); + + ResponseHandler runHandler = verifySessionRunInvoked( connection, bookmark, config, mode, defaultDatabase() ); + assertFalse( cursorFuture.isDone() ); + + // When I response to the run message + runHandler.onSuccess( emptyMap() ); + + // Then + assertEquals( bookmark, bookmarkHolder.getBookmark() ); + assertTrue( cursorFuture.isDone() ); + assertNotNull( cursorFuture.get() ); + } + + private void testRunInUnmanagedTransactionAndWaitForRunResponse( boolean success, AccessMode mode ) throws Exception + { + // Given + Connection connection = connectionMock( mode, protocol ); + + CompletableFuture cursorFuture = + protocol.runInUnmanagedTransaction( connection, QUERY, mock( UnmanagedTransaction.class ), UNLIMITED_FETCH_SIZE ) + .asyncResult() + .toCompletableFuture(); + + ResponseHandler runHandler = verifyTxRunInvoked( connection ); + assertFalse( cursorFuture.isDone() ); + Throwable error = new RuntimeException(); + + if ( success ) + { + runHandler.onSuccess( emptyMap() ); + } + else + { + // When responded with a failure + runHandler.onFailure( error ); + } + + // Then + assertTrue( cursorFuture.isDone() ); + if ( success ) + { + assertNotNull( await( cursorFuture.get().mapSuccessfulRunCompletionAsync() ) ); + } + else + { + Throwable actual = assertThrows( error.getClass(), () -> await( cursorFuture.get().mapSuccessfulRunCompletionAsync() ) ); + assertSame( error, actual ); + } + } + + private void testRunAndWaitForRunResponse( boolean autoCommitTx, TransactionConfig config, AccessMode mode ) throws Exception + { + // Given + Connection connection = connectionMock( mode, protocol ); + Bookmark initialBookmark = InternalBookmark.parse( "neo4j:bookmark:v1:tx987" ); + + CompletionStage cursorStage; + if ( autoCommitTx ) + { + BookmarkHolder bookmarkHolder = new DefaultBookmarkHolder( initialBookmark ); + cursorStage = protocol.runInAutoCommitTransaction( connection, QUERY, bookmarkHolder, config, UNLIMITED_FETCH_SIZE ) + .asyncResult(); + } + else + { + cursorStage = protocol.runInUnmanagedTransaction( connection, QUERY, mock( UnmanagedTransaction.class ), UNLIMITED_FETCH_SIZE ) + .asyncResult(); + } + + // When & Then + CompletableFuture cursorFuture = cursorStage.toCompletableFuture(); + assertFalse( cursorFuture.isDone() ); + + ResponseHandler runResponseHandler = + autoCommitTx ? verifySessionRunInvoked( connection, initialBookmark, config, mode, defaultDatabase() ) : verifyTxRunInvoked( connection ); + runResponseHandler.onSuccess( emptyMap() ); + + assertTrue( cursorFuture.isDone() ); + assertNotNull( cursorFuture.get() ); + } + + private void testDatabaseNameSupport( boolean autoCommitTx ) + { + Connection connection = connectionMock( "foo", protocol ); + if ( autoCommitTx ) + { + ResultCursorFactory factory = + protocol.runInAutoCommitTransaction( connection, QUERY, BookmarkHolder.NO_OP, TransactionConfig.empty(), UNLIMITED_FETCH_SIZE ); + CompletionStage resultStage = factory.asyncResult(); + ResponseHandler runHandler = + verifySessionRunInvoked( connection, InternalBookmark.empty(), TransactionConfig.empty(), AccessMode.WRITE, database( "foo" ) ); + runHandler.onSuccess( emptyMap() ); + await( resultStage ); + verifySessionRunInvoked( connection, InternalBookmark.empty(), TransactionConfig.empty(), AccessMode.WRITE, database( "foo" ) ); + } + else + { + CompletionStage txStage = protocol.beginTransaction( connection, InternalBookmark.empty(), TransactionConfig.empty() ); + await( txStage ); + verifyBeginInvoked( connection, InternalBookmark.empty(), TransactionConfig.empty(), AccessMode.WRITE, database( "foo" ) ); + } + } + + private ResponseHandler verifyTxRunInvoked( Connection connection ) + { + return verifyRunInvoked( connection, RunWithMetadataMessage.unmanagedTxRunMessage( QUERY ) ); + } + + private ResponseHandler verifySessionRunInvoked( Connection connection, Bookmark bookmark, TransactionConfig config, AccessMode mode, + DatabaseName databaseName ) + { + RunWithMetadataMessage runMessage = RunWithMetadataMessage.autoCommitTxRunMessage( QUERY, config, databaseName, mode, bookmark ); + return verifyRunInvoked( connection, runMessage ); + } + + private ResponseHandler verifyRunInvoked( Connection connection, RunWithMetadataMessage runMessage ) + { + ArgumentCaptor runHandlerCaptor = ArgumentCaptor.forClass( ResponseHandler.class ); + ArgumentCaptor pullHandlerCaptor = ArgumentCaptor.forClass( ResponseHandler.class ); + + verify( connection ).write( eq( runMessage ), runHandlerCaptor.capture() ); + verify( connection ).writeAndFlush( any( PullMessage.class ), pullHandlerCaptor.capture() ); + + assertThat( runHandlerCaptor.getValue(), instanceOf( RunResponseHandler.class ) ); + assertThat( pullHandlerCaptor.getValue(), instanceOf( PullAllResponseHandler.class ) ); + + return runHandlerCaptor.getValue(); + } + + private void verifyBeginInvoked( Connection connection, Bookmark bookmark, TransactionConfig config, AccessMode mode, DatabaseName databaseName ) + { + ArgumentCaptor beginHandlerCaptor = ArgumentCaptor.forClass( ResponseHandler.class ); + BeginMessage beginMessage = new BeginMessage( bookmark, config, databaseName, mode ); + verify( connection ).writeAndFlush( eq( beginMessage ), beginHandlerCaptor.capture() ); + assertThat( beginHandlerCaptor.getValue(), instanceOf( BeginTxResponseHandler.class ) ); + } + + private static InternalAuthToken dummyAuthToken() + { + return (InternalAuthToken) AuthTokens.basic( "hello", "world" ); + } +} diff --git a/driver/src/test/java/org/neo4j/driver/internal/messaging/v44/MessageFormatV44Test.java b/driver/src/test/java/org/neo4j/driver/internal/messaging/v44/MessageFormatV44Test.java new file mode 100644 index 0000000000..35e97c3fdf --- /dev/null +++ b/driver/src/test/java/org/neo4j/driver/internal/messaging/v44/MessageFormatV44Test.java @@ -0,0 +1,51 @@ +/* + * 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.v44; + +import org.junit.jupiter.api.Test; + +import org.neo4j.driver.internal.messaging.MessageFormat; +import org.neo4j.driver.internal.messaging.common.CommonMessageReader; +import org.neo4j.driver.internal.packstream.PackInput; +import org.neo4j.driver.internal.packstream.PackOutput; + +import static org.hamcrest.MatcherAssert.assertThat; +import static org.hamcrest.Matchers.instanceOf; +import static org.mockito.Mockito.mock; + +public class MessageFormatV44Test +{ + private static final MessageFormat format = BoltProtocolV44.INSTANCE.createMessageFormat(); + + @Test + void shouldCreateCorrectWriter() + { + MessageFormat.Writer writer = format.newWriter( mock( PackOutput.class ) ); + + assertThat( writer, instanceOf( MessageWriterV44.class ) ); + } + + @Test + void shouldCreateCorrectReader() + { + MessageFormat.Reader reader = format.newReader( mock( PackInput.class ) ); + + assertThat( reader, instanceOf( CommonMessageReader.class ) ); + } +} diff --git a/driver/src/test/java/org/neo4j/driver/internal/messaging/v44/MessageReaderV44Test.java b/driver/src/test/java/org/neo4j/driver/internal/messaging/v44/MessageReaderV44Test.java new file mode 100644 index 0000000000..dd6ee7de80 --- /dev/null +++ b/driver/src/test/java/org/neo4j/driver/internal/messaging/v44/MessageReaderV44Test.java @@ -0,0 +1,119 @@ +/* + * 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.v44; + +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.HashMap; +import java.util.stream.Stream; + +import org.neo4j.driver.Value; +import org.neo4j.driver.Values; +import org.neo4j.driver.internal.InternalPoint2D; +import org.neo4j.driver.internal.InternalPoint3D; +import org.neo4j.driver.internal.messaging.Message; +import org.neo4j.driver.internal.messaging.MessageFormat; +import org.neo4j.driver.internal.messaging.request.DiscardAllMessage; +import org.neo4j.driver.internal.messaging.request.RunMessage; +import org.neo4j.driver.internal.messaging.response.FailureMessage; +import org.neo4j.driver.internal.messaging.response.IgnoredMessage; +import org.neo4j.driver.internal.messaging.response.RecordMessage; +import org.neo4j.driver.internal.messaging.response.SuccessMessage; +import org.neo4j.driver.internal.packstream.PackInput; +import org.neo4j.driver.internal.util.messaging.AbstractMessageReaderTestBase; + +import static java.util.Arrays.asList; +import static java.util.Calendar.APRIL; +import static java.util.Calendar.AUGUST; +import static org.neo4j.driver.Values.parameters; +import static org.neo4j.driver.Values.value; +import static org.neo4j.driver.internal.util.ValueFactory.emptyNodeValue; +import static org.neo4j.driver.internal.util.ValueFactory.emptyPathValue; +import static org.neo4j.driver.internal.util.ValueFactory.emptyRelationshipValue; +import static org.neo4j.driver.internal.util.ValueFactory.filledNodeValue; +import static org.neo4j.driver.internal.util.ValueFactory.filledPathValue; +import static org.neo4j.driver.internal.util.ValueFactory.filledRelationshipValue; + +/** + * The MessageReader under tests is the one provided by the {@link BoltProtocolV44} and not a specific class implementation. + *

+ * It's done on this way to make easy to replace the implementation and still getting the same behaviour. + */ +public class MessageReaderV44Test extends AbstractMessageReaderTestBase +{ + @Override + protected Stream supportedMessages() + { + return Stream.of( + // V2 Record types + record( value( new InternalPoint2D( 42, 120.65, -99.2 ) ) ), + record( value( new InternalPoint3D( 42, 85.391, 98.8, 11.1 ) ) ), + record( value( LocalDate.of( 2012, AUGUST, 3 ) ) ), + record( value( OffsetTime.of( 23, 59, 59, 999, ZoneOffset.MAX ) ) ), + record( value( LocalTime.of( 12, 25 ) ) ), + record( value( LocalDateTime.of( 1999, APRIL, 3, 19, 5, 5, 100_200_300 ) ) ), + record( value( ZonedDateTime.of( 1823, 1, 12, 23, 59, 59, 999_999_999, ZoneOffset.ofHoursMinutes( -7, -15 ) ) ) ), + record( value( ZonedDateTime.of( 1823, 1, 12, 23, 59, 59, 999_999_999, ZoneId.of( "Europe/Stockholm" ) ) ) ), + record( value( Values.isoDuration( Long.MAX_VALUE - 1, Integer.MAX_VALUE - 1, Short.MAX_VALUE - 1, Byte.MAX_VALUE - 1 ).asIsoDuration() ) ), + record( value( Values.isoDuration( 17, 22, 99, 15 ).asIsoDuration() ) ), + + // Bolt previous versions valid messages + new FailureMessage( "Hello", "World!" ), + IgnoredMessage.IGNORED, + new SuccessMessage( new HashMap<>() ), + record( value( 1337L ) ), + record( value( parameters( "cat", null, "dog", null ) ) ), + record( value( parameters( "k", 12, "a", "banana" ) ) ), + record( value( asList( "k", 12, "a", "banana" ) ) ), + + // V3 Record Types + record( emptyNodeValue() ), + record( filledNodeValue() ), + record( emptyRelationshipValue() ), + record( filledRelationshipValue() ), + record( filledPathValue() ), + record( emptyPathValue() ) + ); + } + + @Override + protected Stream unsupportedMessages() + { + return Stream.of( + DiscardAllMessage.DISCARD_ALL, + new RunMessage( "RETURN 42" ) + ); + } + + @Override + protected MessageFormat.Reader newReader( PackInput input ) + { + return BoltProtocolV44.INSTANCE.createMessageFormat().newReader( input ); + } + + private Message record( Value value ) + { + return new RecordMessage( new Value[]{value} ); + } +} 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 new file mode 100644 index 0000000000..cdb827c3eb --- /dev/null +++ b/driver/src/test/java/org/neo4j/driver/internal/messaging/v44/MessageWriterV44Test.java @@ -0,0 +1,152 @@ +/* + * 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.v44; + +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.Collections; +import java.util.HashMap; +import java.util.Map; +import java.util.stream.Stream; + +import org.neo4j.driver.Query; +import org.neo4j.driver.Value; +import org.neo4j.driver.Values; +import org.neo4j.driver.internal.InternalBookmark; +import org.neo4j.driver.internal.messaging.Message; +import org.neo4j.driver.internal.messaging.MessageFormat; +import org.neo4j.driver.internal.messaging.request.BeginMessage; +import org.neo4j.driver.internal.messaging.request.DiscardMessage; +import org.neo4j.driver.internal.messaging.request.HelloMessage; +import org.neo4j.driver.internal.messaging.request.InitMessage; +import org.neo4j.driver.internal.messaging.request.PullMessage; +import org.neo4j.driver.internal.messaging.request.RouteMessage; +import org.neo4j.driver.internal.messaging.request.RunMessage; +import org.neo4j.driver.internal.packstream.PackOutput; +import org.neo4j.driver.internal.security.InternalAuthToken; +import org.neo4j.driver.internal.util.messaging.AbstractMessageWriterTestBase; + +import static java.time.Duration.ofSeconds; +import static java.util.Calendar.DECEMBER; +import static java.util.Collections.emptyMap; +import static java.util.Collections.singletonMap; +import static org.neo4j.driver.AccessMode.READ; +import static org.neo4j.driver.AccessMode.WRITE; +import static org.neo4j.driver.AuthTokens.basic; +import static org.neo4j.driver.Values.point; +import static org.neo4j.driver.Values.value; +import static org.neo4j.driver.internal.DatabaseNameUtil.database; +import static org.neo4j.driver.internal.DatabaseNameUtil.defaultDatabase; +import static org.neo4j.driver.internal.messaging.request.CommitMessage.COMMIT; +import static org.neo4j.driver.internal.messaging.request.DiscardAllMessage.DISCARD_ALL; +import static org.neo4j.driver.internal.messaging.request.GoodbyeMessage.GOODBYE; +import static org.neo4j.driver.internal.messaging.request.PullAllMessage.PULL_ALL; +import static org.neo4j.driver.internal.messaging.request.ResetMessage.RESET; +import static org.neo4j.driver.internal.messaging.request.RollbackMessage.ROLLBACK; +import static org.neo4j.driver.internal.messaging.request.RunWithMetadataMessage.autoCommitTxRunMessage; +import static org.neo4j.driver.internal.messaging.request.RunWithMetadataMessage.unmanagedTxRunMessage; + +/** + * The MessageWriter under tests is the one provided by the {@link BoltProtocolV44} and not a specific class implementation. + *

+ * It's done on this way to make easy to replace the implementation and still getting the same behaviour. + */ +public class MessageWriterV44Test extends AbstractMessageWriterTestBase +{ + @Override + protected MessageFormat.Writer newWriter( PackOutput output ) + { + return BoltProtocolV44.INSTANCE.createMessageFormat().newWriter( output ); + } + + @Override + protected Stream supportedMessages() + { + return Stream.of( + // Bolt V2 Data Types + unmanagedTxRunMessage( new Query( "RETURN $point", singletonMap( "point", point( 42, 12.99, -180.0 ) ) ) ), + unmanagedTxRunMessage( new Query( "RETURN $point", singletonMap( "point", point( 42, 0.51, 2.99, 100.123 ) ) ) ), + unmanagedTxRunMessage( new Query( "RETURN $date", singletonMap( "date", value( LocalDate.ofEpochDay( 2147483650L ) ) ) ) ), + unmanagedTxRunMessage( new Query( "RETURN $time", singletonMap( "time", value( OffsetTime.of( 4, 16, 20, 999, ZoneOffset.MIN ) ) ) ) ), + unmanagedTxRunMessage( new Query( "RETURN $time", singletonMap( "time", value( LocalTime.of( 12, 9, 18, 999_888 ) ) ) ) ), + unmanagedTxRunMessage( + new Query( "RETURN $dateTime", singletonMap( "dateTime", value( LocalDateTime.of( 2049, DECEMBER, 12, 17, 25, 49, 199 ) ) ) ) ), + unmanagedTxRunMessage( new Query( "RETURN $dateTime", singletonMap( "dateTime", value( ZonedDateTime.of( 2000, 1, 10, 12, 2, 49, 300, ZoneOffset + .ofHoursMinutes( 9, 30 ) ) ) ) ) ), + unmanagedTxRunMessage( new Query( "RETURN $dateTime", singletonMap( "dateTime", value( ZonedDateTime.of( 2000, 1, 10, 12, 2, 49, 300, ZoneId.of( + "Europe/Stockholm" ) ) ) ) ) ), + + // New Bolt V4 messages + new PullMessage( 100, 200 ), + new DiscardMessage( 300, 400 ), + + // Bolt V3 messages + new HelloMessage( "MyDriver/1.2.3", ((InternalAuthToken) basic( "neo4j", "neo4j" )).toMap(), Collections.emptyMap() ), + GOODBYE, + new BeginMessage( InternalBookmark.parse( "neo4j:bookmark:v1:tx123" ), ofSeconds( 5 ), singletonMap( "key", value( 42 ) ), READ, + defaultDatabase() ), + new BeginMessage( InternalBookmark.parse( "neo4j:bookmark:v1:tx123" ), ofSeconds( 5 ), singletonMap( "key", value( 42 ) ), WRITE, + database( "foo" ) ), + COMMIT, + ROLLBACK, + + RESET, + autoCommitTxRunMessage( new Query( "RETURN 1" ), ofSeconds( 5 ), singletonMap( "key", value( 42 ) ), defaultDatabase(), READ, + InternalBookmark.parse( "neo4j:bookmark:v1:tx1" ) ), + autoCommitTxRunMessage( new Query( "RETURN 1" ), ofSeconds( 5 ), singletonMap( "key", value( 42 ) ), database( "foo" ), WRITE, + InternalBookmark.parse( "neo4j:bookmark:v1:tx1" ) ), + unmanagedTxRunMessage( new Query( "RETURN 1" ) ), + + // Bolt V3 messages with struct values + autoCommitTxRunMessage( new Query( "RETURN $x", singletonMap( "x", value( ZonedDateTime.now() ) ) ), ofSeconds( 1 ), emptyMap(), + defaultDatabase(), READ, InternalBookmark.empty() ), + autoCommitTxRunMessage( new Query( "RETURN $x", singletonMap( "x", value( ZonedDateTime.now() ) ) ), ofSeconds( 1 ), emptyMap(), + database( "foo" ), + WRITE, InternalBookmark.empty() ), + unmanagedTxRunMessage( new Query( "RETURN $x", singletonMap( "x", point( 42, 1, 2, 3 ) ) ) ), + + // New 4.3 Messages + routeMessage() + ); + } + + @Override + protected Stream unsupportedMessages() + { + return Stream.of( + // Bolt V1, V2 and V3 messages + new InitMessage( "Apa", emptyMap() ), + new RunMessage( "RETURN 1" ), + PULL_ALL, + DISCARD_ALL + ); + } + + private RouteMessage routeMessage() + { + Map routeContext = new HashMap<>(); + routeContext.put( "someContext", Values.value( 124 ) ); + return new RouteMessage( routeContext, InternalBookmark.empty(), "dbName" ); + } +} diff --git a/driver/src/test/java/org/neo4j/driver/util/TestUtil.java b/driver/src/test/java/org/neo4j/driver/util/TestUtil.java index 953e67adc2..1dd3231748 100644 --- a/driver/src/test/java/org/neo4j/driver/util/TestUtil.java +++ b/driver/src/test/java/org/neo4j/driver/util/TestUtil.java @@ -72,6 +72,7 @@ import org.neo4j.driver.internal.messaging.v41.BoltProtocolV41; import org.neo4j.driver.internal.messaging.v42.BoltProtocolV42; import org.neo4j.driver.internal.messaging.v43.BoltProtocolV43; +import org.neo4j.driver.internal.messaging.v44.BoltProtocolV44; import org.neo4j.driver.internal.retry.RetryLogic; import org.neo4j.driver.internal.spi.Connection; import org.neo4j.driver.internal.spi.ConnectionProvider; @@ -519,7 +520,7 @@ public static Connection connectionMock( String databaseName, AccessMode mode, B BoltProtocolVersion version = protocol.version(); if ( version.equals( BoltProtocolV3.VERSION ) || version.equals( BoltProtocolV4.VERSION ) || version.equals( BoltProtocolV41.VERSION ) || version.equals( BoltProtocolV42.VERSION ) || - version.equals( BoltProtocolV43.VERSION ) ) + version.equals( BoltProtocolV43.VERSION ) || version.equals( BoltProtocolV44.VERSION ) ) { setupSuccessResponse( connection, CommitMessage.class ); setupSuccessResponse( connection, RollbackMessage.class );