diff --git a/community/bolt/src/main/java/org/neo4j/bolt/logging/BoltMessageLogger.java b/community/bolt/src/main/java/org/neo4j/bolt/logging/BoltMessageLogger.java index 601a580ff9773..0d715071b2ad4 100644 --- a/community/bolt/src/main/java/org/neo4j/bolt/logging/BoltMessageLogger.java +++ b/community/bolt/src/main/java/org/neo4j/bolt/logging/BoltMessageLogger.java @@ -40,7 +40,7 @@ public interface BoltMessageLogger void serverError( String eventName, Status status ); - void logUserAgent( String userAgent ); + void logInit( String userAgent ); void logRun(); diff --git a/community/bolt/src/main/java/org/neo4j/bolt/logging/BoltMessageLoggerImpl.java b/community/bolt/src/main/java/org/neo4j/bolt/logging/BoltMessageLoggerImpl.java index fe35fe7e0caf5..36759650ea3ba 100644 --- a/community/bolt/src/main/java/org/neo4j/bolt/logging/BoltMessageLoggerImpl.java +++ b/community/bolt/src/main/java/org/neo4j/bolt/logging/BoltMessageLoggerImpl.java @@ -102,7 +102,7 @@ public void serverError( String eventName, Status status ) } @Override - public void logUserAgent( String userAgent ) + public void logInit( String userAgent ) { clientEvent( "INIT", () -> userAgent); } diff --git a/community/bolt/src/main/java/org/neo4j/bolt/logging/NullBoltMessageLogger.java b/community/bolt/src/main/java/org/neo4j/bolt/logging/NullBoltMessageLogger.java index 9e55a2199981e..43512fc088a20 100644 --- a/community/bolt/src/main/java/org/neo4j/bolt/logging/NullBoltMessageLogger.java +++ b/community/bolt/src/main/java/org/neo4j/bolt/logging/NullBoltMessageLogger.java @@ -73,7 +73,7 @@ public void serverError( String eventName, Status status ) } @Override - public void logUserAgent( String userAgent ) + public void logInit( String userAgent ) { } diff --git a/community/bolt/src/main/java/org/neo4j/bolt/runtime/BoltStateMachineFactoryImpl.java b/community/bolt/src/main/java/org/neo4j/bolt/runtime/BoltStateMachineFactoryImpl.java index d094159b5f459..c2136adbcb3f8 100644 --- a/community/bolt/src/main/java/org/neo4j/bolt/runtime/BoltStateMachineFactoryImpl.java +++ b/community/bolt/src/main/java/org/neo4j/bolt/runtime/BoltStateMachineFactoryImpl.java @@ -75,7 +75,7 @@ else if ( protocolVersion == BoltProtocolV3.VERSION ) } else { - return null; + throw new IllegalArgumentException( "Failed to create a state machine for protocol version " + protocolVersion ); } } diff --git a/community/bolt/src/main/java/org/neo4j/bolt/runtime/StatementProcessor.java b/community/bolt/src/main/java/org/neo4j/bolt/runtime/StatementProcessor.java index 3df07c5059bc7..8bb212dab9bc2 100644 --- a/community/bolt/src/main/java/org/neo4j/bolt/runtime/StatementProcessor.java +++ b/community/bolt/src/main/java/org/neo4j/bolt/runtime/StatementProcessor.java @@ -31,9 +31,11 @@ public interface StatementProcessor StatementMetadata run( String statement, MapValue params ) throws KernelException; + StatementMetadata run( String statement, MapValue params, Bookmark bookmark ) throws KernelException; + void streamResult( ThrowingConsumer resultConsumer ) throws Exception; - void commitTransaction() throws KernelException; + Bookmark commitTransaction() throws KernelException; void rollbackTransaction() throws KernelException; @@ -63,6 +65,12 @@ public StatementMetadata run( String statement, MapValue params ) throws KernelE throw new UnsupportedOperationException( "Unable to run statements" ); } + @Override + public StatementMetadata run( String statement, MapValue params, Bookmark bookmark ) throws KernelException + { + throw new UnsupportedOperationException( "Unable to run statements" ); + } + @Override public void streamResult( ThrowingConsumer resultConsumer ) throws Exception { @@ -70,7 +78,7 @@ public void streamResult( ThrowingConsumer resultConsumer } @Override - public void commitTransaction() throws KernelException + public Bookmark commitTransaction() throws KernelException { throw new UnsupportedOperationException( "Unable to commit a transaction" ); } diff --git a/community/bolt/src/main/java/org/neo4j/bolt/v1/messaging/decoder/InitMessageDecoder.java b/community/bolt/src/main/java/org/neo4j/bolt/v1/messaging/decoder/InitMessageDecoder.java index ff63247ed4b86..9d56a9ee78d7e 100644 --- a/community/bolt/src/main/java/org/neo4j/bolt/v1/messaging/decoder/InitMessageDecoder.java +++ b/community/bolt/src/main/java/org/neo4j/bolt/v1/messaging/decoder/InitMessageDecoder.java @@ -59,7 +59,7 @@ public RequestMessage decode( Neo4jPack.Unpacker unpacker ) throws IOException { String userAgent = unpacker.unpackString(); Map authToken = readAuthToken( unpacker ); - messageLogger.logUserAgent( userAgent ); + messageLogger.logInit( userAgent ); return new InitMessage( userAgent, authToken ); } diff --git a/community/bolt/src/main/java/org/neo4j/bolt/v1/runtime/BoltStateMachineV1SPI.java b/community/bolt/src/main/java/org/neo4j/bolt/v1/runtime/BoltStateMachineV1SPI.java index fef706b350190..b0a2b7b120bc1 100644 --- a/community/bolt/src/main/java/org/neo4j/bolt/v1/runtime/BoltStateMachineV1SPI.java +++ b/community/bolt/src/main/java/org/neo4j/bolt/v1/runtime/BoltStateMachineV1SPI.java @@ -37,6 +37,7 @@ public class BoltStateMachineV1SPI implements BoltStateMachineSPI { + public static final String BOLT_SERVER_VERSION_PREFIX = "Neo4j/"; private final BoltConnectionDescriptor connectionDescriptor; private final UsageData usageData; private final ErrorReporter errorReporter; @@ -54,7 +55,7 @@ public BoltStateMachineV1SPI( BoltConnectionDescriptor connectionDescriptor, Usa this.connectionTracker = connectionTracker; this.authentication = authentication; this.transactionSpi = transactionStateMachineSPI; - this.version = "Neo4j/" + Version.getNeo4jVersion(); + this.version = BOLT_SERVER_VERSION_PREFIX + Version.getNeo4jVersion(); } @Override diff --git a/community/bolt/src/main/java/org/neo4j/bolt/v1/runtime/TransactionStateMachine.java b/community/bolt/src/main/java/org/neo4j/bolt/v1/runtime/TransactionStateMachine.java index 9f4d3ccd78004..b60193dd7c055 100644 --- a/community/bolt/src/main/java/org/neo4j/bolt/v1/runtime/TransactionStateMachine.java +++ b/community/bolt/src/main/java/org/neo4j/bolt/v1/runtime/TransactionStateMachine.java @@ -75,7 +75,7 @@ public void beginTransaction( Bookmark bookmark ) throws KernelException { ensureNoPendingTerminationNotice(); - state = state.beginTransaction( bookmark, ctx, spi ); + state = state.beginTransaction( ctx, spi, bookmark ); } finally { @@ -85,13 +85,19 @@ public void beginTransaction( Bookmark bookmark ) throws KernelException @Override public StatementMetadata run( String statement, MapValue params ) throws KernelException + { + return run( statement, params, null ); + } + + @Override + public StatementMetadata run( String statement, MapValue params, Bookmark bookmark ) throws KernelException { before(); try { ensureNoPendingTerminationNotice(); - state = state.run( ctx, spi, statement, params ); + state = state.run( ctx, spi, statement, params, bookmark ); return ctx.currentStatementMetadata; } @@ -118,7 +124,7 @@ public void streamResult( ThrowingConsumer resultConsumer } @Override - public void commitTransaction() throws KernelException + public Bookmark commitTransaction() throws KernelException { before(); try @@ -126,6 +132,7 @@ public void commitTransaction() throws KernelException ensureNoPendingTerminationNotice(); state = state.commitTransaction( ctx, spi ); + return newestBookmark( spi ); } catch ( TransactionFailureException ex ) { @@ -239,10 +246,16 @@ enum State AUTO_COMMIT { @Override - State beginTransaction( Bookmark bookmark, MutableTransactionState ctx, TransactionStateMachineSPI spi ) throws KernelException + State beginTransaction( MutableTransactionState ctx, TransactionStateMachineSPI spi, Bookmark bookmark ) throws KernelException { + waitForBookmark( ctx, spi, bookmark ); ctx.currentTransaction = spi.beginTransaction( ctx.loginContext ); + return EXPLICIT_TRANSACTION; + } + private void waitForBookmark( MutableTransactionState ctx, TransactionStateMachineSPI spi, Bookmark bookmark ) + throws TransactionFailureException + { if ( bookmark != null ) { spi.awaitUpToDate( bookmark.txId() ); @@ -252,12 +265,19 @@ State beginTransaction( Bookmark bookmark, MutableTransactionState ctx, Transact { ctx.currentResult = BoltResult.EMPTY; } - - return EXPLICIT_TRANSACTION; } @Override - State run( MutableTransactionState ctx, TransactionStateMachineSPI spi, String statement, MapValue params ) throws KernelException + State run( MutableTransactionState ctx, TransactionStateMachineSPI spi, String statement, MapValue params, Bookmark bookmark ) + throws KernelException + { + statement = parseStatement( ctx, statement ); + waitForBookmark( ctx, spi, bookmark ); + execute( ctx, spi, statement, params, spi.isPeriodicCommit( statement ) ); + return AUTO_COMMIT; + } + + private String parseStatement( MutableTransactionState ctx, String statement ) { if ( statement.isEmpty() ) { @@ -267,8 +287,7 @@ State run( MutableTransactionState ctx, TransactionStateMachineSPI spi, String s { ctx.lastStatement = statement; } - execute( ctx, spi, statement, params, spi.isPeriodicCommit( statement ) ); - return AUTO_COMMIT; + return statement; } void execute( MutableTransactionState ctx, TransactionStateMachineSPI spi, String statement, MapValue params, boolean isPeriodicCommit ) @@ -336,13 +355,14 @@ State rollbackTransaction( MutableTransactionState ctx, TransactionStateMachineS EXPLICIT_TRANSACTION { @Override - State beginTransaction( Bookmark bookmark, MutableTransactionState ctx, TransactionStateMachineSPI spi ) throws KernelException + State beginTransaction( MutableTransactionState ctx, TransactionStateMachineSPI spi, Bookmark bookmark ) throws KernelException { throw new QueryExecutionKernelException( new InvalidSemanticsException( "Nested transactions are not supported." ) ); } @Override - State run( MutableTransactionState ctx, TransactionStateMachineSPI spi, String statement, MapValue params ) throws KernelException + State run( MutableTransactionState ctx, TransactionStateMachineSPI spi, String statement, MapValue params, Bookmark bookmark ) + throws KernelException { if ( statement.isEmpty() ) { @@ -378,8 +398,7 @@ void streamResult( MutableTransactionState ctx, State commitTransaction( MutableTransactionState ctx, TransactionStateMachineSPI spi ) throws KernelException { closeTransaction( ctx, true ); - long txId = spi.newestEncounteredTxId(); - Bookmark bookmark = new Bookmark( txId ); + Bookmark bookmark = newestBookmark( spi ); ctx.currentResult = new BookmarkResult( bookmark ); return AUTO_COMMIT; } @@ -393,9 +412,10 @@ State rollbackTransaction( MutableTransactionState ctx, TransactionStateMachineS } }; - abstract State beginTransaction( Bookmark bookmark, MutableTransactionState ctx, TransactionStateMachineSPI spi ) throws KernelException; + abstract State beginTransaction( MutableTransactionState ctx, TransactionStateMachineSPI spi, Bookmark bookmark ) throws KernelException; - abstract State run( MutableTransactionState ctx, TransactionStateMachineSPI spi, String statement, MapValue params ) throws KernelException; + abstract State run( MutableTransactionState ctx, TransactionStateMachineSPI spi, String statement, MapValue params, Bookmark bookmark ) + throws KernelException; abstract void streamResult( MutableTransactionState ctx, ThrowingConsumer resultConsumer ) throws Exception; @@ -490,6 +510,12 @@ void startExecution( MutableTransactionState ctx, BoltResultHandle resultHandle } + private static Bookmark newestBookmark( TransactionStateMachineSPI spi ) + { + long txId = spi.newestEncounteredTxId(); + return new Bookmark( txId ); + } + static class MutableTransactionState { /** The current session security context to be used for starting transactions */ diff --git a/community/bolt/src/main/java/org/neo4j/bolt/v1/runtime/bookmarking/Bookmark.java b/community/bolt/src/main/java/org/neo4j/bolt/v1/runtime/bookmarking/Bookmark.java index 51ee954d95848..97125b65e64ee 100644 --- a/community/bolt/src/main/java/org/neo4j/bolt/v1/runtime/bookmarking/Bookmark.java +++ b/community/bolt/src/main/java/org/neo4j/bolt/v1/runtime/bookmarking/Bookmark.java @@ -21,6 +21,7 @@ import java.util.Objects; +import org.neo4j.bolt.runtime.BoltResponseHandler; import org.neo4j.internal.kernel.api.exceptions.KernelException; import org.neo4j.kernel.api.exceptions.Status; import org.neo4j.values.AnyValue; @@ -30,6 +31,7 @@ import org.neo4j.values.virtual.MapValue; import static java.lang.String.format; +import static org.neo4j.values.storable.Values.stringValue; public class Bookmark { @@ -154,6 +156,11 @@ private static long txIdFrom( AnyValue bookmark ) throws BookmarkFormatException } } + public void attachTo( BoltResponseHandler state ) + { + state.onMetadata( BOOKMARK_KEY, stringValue( toString() ) ); + } + static class BookmarkFormatException extends KernelException { BookmarkFormatException( String bookmarkString, NumberFormatException e ) diff --git a/community/bolt/src/main/java/org/neo4j/bolt/v3/BoltStateMachineV3.java b/community/bolt/src/main/java/org/neo4j/bolt/v3/BoltStateMachineV3.java index eefb7a79c3fb3..71c1b063882e4 100644 --- a/community/bolt/src/main/java/org/neo4j/bolt/v3/BoltStateMachineV3.java +++ b/community/bolt/src/main/java/org/neo4j/bolt/v3/BoltStateMachineV3.java @@ -24,12 +24,13 @@ import org.neo4j.bolt.BoltChannel; import org.neo4j.bolt.runtime.BoltStateMachineSPI; import org.neo4j.bolt.v1.runtime.BoltStateMachineV1; -import org.neo4j.bolt.v1.runtime.ConnectedState; -import org.neo4j.bolt.v1.runtime.FailedState; import org.neo4j.bolt.v1.runtime.InterruptedState; -import org.neo4j.bolt.v1.runtime.ReadyState; -import org.neo4j.bolt.v1.runtime.StreamingState; +import org.neo4j.bolt.v3.runtime.FailedState; +import org.neo4j.bolt.v3.runtime.DefunctState; import org.neo4j.bolt.v3.runtime.ExtraMetaDataConnectedState; +import org.neo4j.bolt.v3.runtime.ReadyState; +import org.neo4j.bolt.v3.runtime.StreamingState; +import org.neo4j.bolt.v3.runtime.TransactionReadyState; public class BoltStateMachineV3 extends BoltStateMachineV1 { @@ -41,15 +42,19 @@ public BoltStateMachineV3( BoltStateMachineSPI boltSPI, BoltChannel boltChannel, @Override protected States buildStates() { - ConnectedState connected = new ExtraMetaDataConnectedState(); + ExtraMetaDataConnectedState connected = new ExtraMetaDataConnectedState(); ReadyState ready = new ReadyState(); StreamingState streaming = new StreamingState(); FailedState failed = new FailedState(); InterruptedState interrupted = new InterruptedState(); + DefunctState defunct = new DefunctState(); + TransactionReadyState txReady = new TransactionReadyState(); + StreamingState txStreaming = new StreamingState(); connected.setReadyState( ready ); - connected.setFailedState( failed ); + connected.setFailedState( defunct ); + ready.setTransactionReadyState( txReady ); ready.setStreamingState( streaming ); ready.setInterruptedState( interrupted ); ready.setFailedState( failed ); @@ -58,11 +63,19 @@ protected States buildStates() streaming.setInterruptedState( interrupted ); streaming.setFailedState( failed ); - failed.setReadyState( ready ); + txReady.setReadyState( ready ); + txReady.setTransactionStreamingState( txStreaming ); + txReady.setInterruptedState( interrupted ); + txReady.setFailedState( failed ); + + txStreaming.setReadyState( txReady ); + txStreaming.setInterruptedState( interrupted ); + txStreaming.setFailedState( failed ); + failed.setInterruptedState( interrupted ); interrupted.setReadyState( ready ); - interrupted.setFailedState( failed ); + interrupted.setFailedState( defunct ); return new States( connected, failed ); } diff --git a/community/bolt/src/main/java/org/neo4j/bolt/v3/messaging/BoltRequestMessageReaderV3.java b/community/bolt/src/main/java/org/neo4j/bolt/v3/messaging/BoltRequestMessageReaderV3.java index 7456bdbd7463d..ed23c158eb4fe 100644 --- a/community/bolt/src/main/java/org/neo4j/bolt/v3/messaging/BoltRequestMessageReaderV3.java +++ b/community/bolt/src/main/java/org/neo4j/bolt/v3/messaging/BoltRequestMessageReaderV3.java @@ -30,11 +30,14 @@ import org.neo4j.bolt.runtime.BoltResponseHandler; import org.neo4j.bolt.v1.messaging.MessageProcessingHandler; import org.neo4j.bolt.v1.messaging.ResultHandler; -import org.neo4j.bolt.v1.messaging.decoder.AckFailureMessageDecoder; import org.neo4j.bolt.v1.messaging.decoder.DiscardAllMessageDecoder; import org.neo4j.bolt.v1.messaging.decoder.PullAllMessageDecoder; import org.neo4j.bolt.v1.messaging.decoder.ResetMessageDecoder; -import org.neo4j.bolt.v1.messaging.decoder.RunMessageDecoder; +import org.neo4j.bolt.v3.messaging.decoder.BeginMessageDecoder; +import org.neo4j.bolt.v3.messaging.decoder.CommitMessageDecoder; +import org.neo4j.bolt.v3.messaging.decoder.HelloMessageDecoder; +import org.neo4j.bolt.v3.messaging.decoder.RollbackMessageDecoder; +import org.neo4j.bolt.v3.messaging.decoder.RunMessageDecoder; import org.neo4j.kernel.impl.logging.LogService; import org.neo4j.logging.Log; @@ -44,7 +47,7 @@ public BoltRequestMessageReaderV3( BoltConnection connection, BoltResponseMessag BoltMessageLogger messageLogger, LogService logService ) { super( connection, - newSimpleResponseHandler( connection, responseMessageWriter, logService ), + newSimpleResponseHandler( responseMessageWriter, connection, logService ), buildDecoders( connection, responseMessageWriter, messageLogger, logService ), messageLogger ); } @@ -52,23 +55,23 @@ public BoltRequestMessageReaderV3( BoltConnection connection, BoltResponseMessag private static List buildDecoders( BoltConnection connection, BoltResponseMessageWriter responseMessageWriter, BoltMessageLogger messageLogger, LogService logService ) { - BoltResponseHandler helloHandler = newSimpleResponseHandler( connection, responseMessageWriter, logService ); - BoltResponseHandler runHandler = newSimpleResponseHandler( connection, responseMessageWriter, logService ); BoltResponseHandler resultHandler = new ResultHandler( responseMessageWriter, connection, internalLog( logService ) ); - BoltResponseHandler defaultHandler = newSimpleResponseHandler( connection, responseMessageWriter, logService ); + BoltResponseHandler defaultHandler = newSimpleResponseHandler( responseMessageWriter, connection, logService ); return Arrays.asList( - new HelloMessageDecoder( helloHandler, messageLogger ), - new AckFailureMessageDecoder( defaultHandler, messageLogger ), - new ResetMessageDecoder( connection, defaultHandler, messageLogger ), - new RunMessageDecoder( runHandler, messageLogger ), + new HelloMessageDecoder( defaultHandler ), + new RunMessageDecoder( defaultHandler ), new DiscardAllMessageDecoder( resultHandler, messageLogger ), - new PullAllMessageDecoder( resultHandler, messageLogger ) + new PullAllMessageDecoder( resultHandler, messageLogger ), + new BeginMessageDecoder( defaultHandler ), + new CommitMessageDecoder( resultHandler ), + new RollbackMessageDecoder( resultHandler ), + new ResetMessageDecoder( connection, defaultHandler, messageLogger ) ); } - private static BoltResponseHandler newSimpleResponseHandler( BoltConnection connection, - BoltResponseMessageWriter responseMessageWriter, LogService logService ) + private static BoltResponseHandler newSimpleResponseHandler( BoltResponseMessageWriter responseMessageWriter, BoltConnection connection, + LogService logService ) { return new MessageProcessingHandler( responseMessageWriter, connection, internalLog( logService ) ); } diff --git a/community/bolt/src/main/java/org/neo4j/bolt/v3/messaging/decoder/BeginMessageDecoder.java b/community/bolt/src/main/java/org/neo4j/bolt/v3/messaging/decoder/BeginMessageDecoder.java new file mode 100644 index 0000000000000..42ec6b0aacaa1 --- /dev/null +++ b/community/bolt/src/main/java/org/neo4j/bolt/v3/messaging/decoder/BeginMessageDecoder.java @@ -0,0 +1,59 @@ +/* + * Copyright (c) 2002-2018 "Neo4j," + * Neo4j Sweden AB [http://neo4j.com] + * + * This file is part of Neo4j. + * + * Neo4j is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ +package org.neo4j.bolt.v3.messaging.decoder; + +import java.io.IOException; + +import org.neo4j.bolt.messaging.Neo4jPack; +import org.neo4j.bolt.messaging.RequestMessage; +import org.neo4j.bolt.messaging.RequestMessageDecoder; +import org.neo4j.bolt.runtime.BoltResponseHandler; +import org.neo4j.bolt.v3.messaging.request.BeginMessage; +import org.neo4j.values.virtual.MapValue; + +public class BeginMessageDecoder implements RequestMessageDecoder +{ + private final BoltResponseHandler responseHandler; + + public BeginMessageDecoder( BoltResponseHandler responseHandler ) + { + this.responseHandler = responseHandler; + } + + @Override + public int signature() + { + return BeginMessage.SIGNATURE; + } + + @Override + public BoltResponseHandler responseHandler() + { + return responseHandler; + } + + @Override + public RequestMessage decode( Neo4jPack.Unpacker unpacker ) throws IOException + { + MapValue meta = unpacker.unpackMap(); + return new BeginMessage( meta ); + } +} + diff --git a/community/bolt/src/main/java/org/neo4j/bolt/v3/messaging/decoder/CommitMessageDecoder.java b/community/bolt/src/main/java/org/neo4j/bolt/v3/messaging/decoder/CommitMessageDecoder.java new file mode 100644 index 0000000000000..0d5eb9de5fba2 --- /dev/null +++ b/community/bolt/src/main/java/org/neo4j/bolt/v3/messaging/decoder/CommitMessageDecoder.java @@ -0,0 +1,59 @@ +/* + * Copyright (c) 2002-2018 "Neo4j," + * Neo4j Sweden AB [http://neo4j.com] + * + * This file is part of Neo4j. + * + * Neo4j is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ +package org.neo4j.bolt.v3.messaging.decoder; + +import java.io.IOException; + +import org.neo4j.bolt.messaging.Neo4jPack; +import org.neo4j.bolt.messaging.RequestMessage; +import org.neo4j.bolt.messaging.RequestMessageDecoder; +import org.neo4j.bolt.runtime.BoltResponseHandler; +import org.neo4j.bolt.v3.messaging.request.CommitMessage; + +import static org.neo4j.bolt.v3.messaging.request.CommitMessage.COMMIT_MESSAGE; + +public class CommitMessageDecoder implements RequestMessageDecoder +{ + private final BoltResponseHandler responseHandler; + + public CommitMessageDecoder( BoltResponseHandler responseHandler ) + { + this.responseHandler = responseHandler; + } + + @Override + public int signature() + { + return CommitMessage.SIGNATURE; + } + + @Override + public BoltResponseHandler responseHandler() + { + return responseHandler; + } + + @Override + public RequestMessage decode( Neo4jPack.Unpacker unpacker ) throws IOException + { + return COMMIT_MESSAGE; + } +} + diff --git a/community/bolt/src/main/java/org/neo4j/bolt/v3/messaging/HelloMessageDecoder.java b/community/bolt/src/main/java/org/neo4j/bolt/v3/messaging/decoder/HelloMessageDecoder.java similarity index 84% rename from community/bolt/src/main/java/org/neo4j/bolt/v3/messaging/HelloMessageDecoder.java rename to community/bolt/src/main/java/org/neo4j/bolt/v3/messaging/decoder/HelloMessageDecoder.java index 8b51e5a100ca6..ef6fb1d50bd6a 100644 --- a/community/bolt/src/main/java/org/neo4j/bolt/v3/messaging/HelloMessageDecoder.java +++ b/community/bolt/src/main/java/org/neo4j/bolt/v3/messaging/decoder/HelloMessageDecoder.java @@ -17,29 +17,27 @@ * You should have received a copy of the GNU General Public License * along with this program. If not, see . */ -package org.neo4j.bolt.v3.messaging; +package org.neo4j.bolt.v3.messaging.decoder; import java.io.IOException; import java.util.HashMap; import java.util.Map; -import org.neo4j.bolt.logging.BoltMessageLogger; import org.neo4j.bolt.messaging.Neo4jPack; import org.neo4j.bolt.messaging.RequestMessage; import org.neo4j.bolt.messaging.RequestMessageDecoder; import org.neo4j.bolt.runtime.BoltResponseHandler; import org.neo4j.bolt.v1.messaging.decoder.PrimitiveOnlyValueWriter; +import org.neo4j.bolt.v3.messaging.request.HelloMessage; import org.neo4j.values.virtual.MapValue; public class HelloMessageDecoder implements RequestMessageDecoder { private final BoltResponseHandler responseHandler; - private final BoltMessageLogger messageLogger; - public HelloMessageDecoder( BoltResponseHandler responseHandler, BoltMessageLogger messageLogger ) + public HelloMessageDecoder( BoltResponseHandler responseHandler ) { this.responseHandler = responseHandler; - this.messageLogger = messageLogger; } @Override @@ -61,9 +59,6 @@ public RequestMessage decode( Neo4jPack.Unpacker unpacker ) throws IOException PrimitiveOnlyValueWriter writer = new PrimitiveOnlyValueWriter(); Map meta = new HashMap<>( helloMeta.size() ); helloMeta.foreach( ( key, value ) -> meta.put( key, writer.valueAsObject( value ) ) ); - HelloMessage helloMessage = new HelloMessage( meta ); - - messageLogger.logUserAgent( helloMessage.userAgent() ); - return helloMessage; + return new HelloMessage( meta ); } } diff --git a/community/bolt/src/main/java/org/neo4j/bolt/v3/messaging/decoder/RollbackMessageDecoder.java b/community/bolt/src/main/java/org/neo4j/bolt/v3/messaging/decoder/RollbackMessageDecoder.java new file mode 100644 index 0000000000000..95619fd08e45d --- /dev/null +++ b/community/bolt/src/main/java/org/neo4j/bolt/v3/messaging/decoder/RollbackMessageDecoder.java @@ -0,0 +1,59 @@ +/* + * Copyright (c) 2002-2018 "Neo4j," + * Neo4j Sweden AB [http://neo4j.com] + * + * This file is part of Neo4j. + * + * Neo4j is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ +package org.neo4j.bolt.v3.messaging.decoder; + +import java.io.IOException; + +import org.neo4j.bolt.messaging.Neo4jPack; +import org.neo4j.bolt.messaging.RequestMessage; +import org.neo4j.bolt.messaging.RequestMessageDecoder; +import org.neo4j.bolt.runtime.BoltResponseHandler; + +import static org.neo4j.bolt.v3.messaging.request.RollbackMessage.ROLLBACK_MESSAGE; +import static org.neo4j.bolt.v3.messaging.request.RollbackMessage.SIGNATURE; + +public class RollbackMessageDecoder implements RequestMessageDecoder +{ + private final BoltResponseHandler responseHandler; + + public RollbackMessageDecoder( BoltResponseHandler responseHandler ) + { + this.responseHandler = responseHandler; + } + + @Override + public int signature() + { + return SIGNATURE; + } + + @Override + public BoltResponseHandler responseHandler() + { + return responseHandler; + } + + @Override + public RequestMessage decode( Neo4jPack.Unpacker unpacker ) throws IOException + { + return ROLLBACK_MESSAGE; + } +} + diff --git a/community/bolt/src/main/java/org/neo4j/bolt/v3/messaging/decoder/RunMessageDecoder.java b/community/bolt/src/main/java/org/neo4j/bolt/v3/messaging/decoder/RunMessageDecoder.java new file mode 100644 index 0000000000000..4428f0bd3446e --- /dev/null +++ b/community/bolt/src/main/java/org/neo4j/bolt/v3/messaging/decoder/RunMessageDecoder.java @@ -0,0 +1,63 @@ +/* + * Copyright (c) 2002-2018 "Neo4j," + * Neo4j Sweden AB [http://neo4j.com] + * + * This file is part of Neo4j. + * + * Neo4j is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ +package org.neo4j.bolt.v3.messaging.decoder; + +import java.io.IOException; + +import org.neo4j.bolt.messaging.Neo4jPack; +import org.neo4j.bolt.messaging.RequestMessage; +import org.neo4j.bolt.messaging.RequestMessageDecoder; +import org.neo4j.bolt.runtime.BoltResponseHandler; +import org.neo4j.bolt.v3.messaging.request.RunMessage; +import org.neo4j.values.virtual.MapValue; + +import static org.neo4j.bolt.v3.messaging.request.RunMessage.SIGNATURE; + +public class RunMessageDecoder implements RequestMessageDecoder +{ + private final BoltResponseHandler responseHandler; + + public RunMessageDecoder( BoltResponseHandler responseHandler ) + { + this.responseHandler = responseHandler; + } + + @Override + public int signature() + { + return SIGNATURE; + } + + @Override + public BoltResponseHandler responseHandler() + { + return responseHandler; + } + + @Override + public RequestMessage decode( Neo4jPack.Unpacker unpacker ) throws IOException + { + String statement = unpacker.unpackString(); + MapValue params = unpacker.unpackMap(); + MapValue meta = unpacker.unpackMap(); + return new RunMessage( statement, params, meta ); + } +} + diff --git a/community/bolt/src/main/java/org/neo4j/bolt/v3/messaging/decoder/StatementMode.java b/community/bolt/src/main/java/org/neo4j/bolt/v3/messaging/decoder/StatementMode.java new file mode 100644 index 0000000000000..85a7c6cd8b3f7 --- /dev/null +++ b/community/bolt/src/main/java/org/neo4j/bolt/v3/messaging/decoder/StatementMode.java @@ -0,0 +1,54 @@ +/* + * Copyright (c) 2002-2018 "Neo4j," + * Neo4j Sweden AB [http://neo4j.com] + * + * This file is part of Neo4j. + * + * Neo4j is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ +package org.neo4j.bolt.v3.messaging.decoder; + +import org.neo4j.bolt.messaging.BoltIOException; +import org.neo4j.kernel.api.exceptions.Status; + +public enum StatementMode //TODO is this already somewhere? +{ + READ( "R" ), + WRITE( "W" ); + + private final String signature; + + StatementMode( String name ) + { + this.signature = name; + } + + public String signature() + { + return this.signature; + } + + public static StatementMode parseMode( String str ) throws BoltIOException + { + if ( str.equalsIgnoreCase( READ.signature() ) ) + { + return READ; + } + else if ( str.equalsIgnoreCase( WRITE.signature() ) ) + { + return WRITE; + } + throw new BoltIOException( Status.Request.InvalidFormat, String.format( "Unknown mode '%s' for input cypher statement.", str ) ); + } +} diff --git a/community/bolt/src/main/java/org/neo4j/bolt/v3/messaging/request/BeginMessage.java b/community/bolt/src/main/java/org/neo4j/bolt/v3/messaging/request/BeginMessage.java new file mode 100644 index 0000000000000..9068ed7e005f0 --- /dev/null +++ b/community/bolt/src/main/java/org/neo4j/bolt/v3/messaging/request/BeginMessage.java @@ -0,0 +1,147 @@ +/* + * Copyright (c) 2002-2018 "Neo4j," + * Neo4j Sweden AB [http://neo4j.com] + * + * This file is part of Neo4j. + * + * Neo4j is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ +package org.neo4j.bolt.v3.messaging.request; + +import java.time.Duration; +import java.util.Objects; + +import org.neo4j.bolt.messaging.BoltIOException; +import org.neo4j.bolt.messaging.RequestMessage; +import org.neo4j.bolt.v1.runtime.bookmarking.Bookmark; +import org.neo4j.bolt.v3.messaging.decoder.StatementMode; +import org.neo4j.internal.kernel.api.exceptions.KernelException; +import org.neo4j.kernel.api.exceptions.Status; +import org.neo4j.values.AnyValue; +import org.neo4j.values.storable.LongValue; +import org.neo4j.values.storable.TextValue; +import org.neo4j.values.virtual.MapValue; +import org.neo4j.values.virtual.VirtualValues; + +import static java.util.Objects.requireNonNull; + +public class BeginMessage implements RequestMessage +{ + private final MapValue meta; + private final StatementMode mode; + private final Bookmark bookmark; + private final Duration txTimeout; + + private static final String MODE_KEY = "mode"; + private static final String TX_TIMEOUT_KEY = "tx_timeout"; + public static final byte SIGNATURE = 0x11; + + public BeginMessage() throws BoltIOException + { + this( VirtualValues.EMPTY_MAP ); + } + + public BeginMessage( MapValue meta ) throws BoltIOException + { + this.meta = requireNonNull( meta ); + this.bookmark = parseBookmark( meta ); + this.mode = parseStatementMode( meta ); + this.txTimeout = parseTransactionTimeout( meta ); + } + + static Bookmark parseBookmark( MapValue meta ) throws BoltIOException + { + try + { + return Bookmark.fromParamsOrNull( meta ); + } + catch ( KernelException e ) + { + throw new BoltIOException( Status.Request.InvalidFormat, e.getMessage(), e ); + } + } + + static Duration parseTransactionTimeout( MapValue meta ) + { + AnyValue anyValue = meta.get( TX_TIMEOUT_KEY ); + if ( anyValue instanceof LongValue ) + { + return Duration.ofMillis( ((LongValue) anyValue).longValue() ); + } + return null; + } + + static StatementMode parseStatementMode( MapValue meta ) throws BoltIOException + { + AnyValue anyValue = meta.get( MODE_KEY ); + if ( anyValue instanceof TextValue ) + { + return StatementMode.parseMode( ((TextValue) anyValue).stringValue() ); + } + return null; + } + + public Bookmark bookmark() + { + return this.bookmark; + } + + public StatementMode mode() + { + return this.mode; + } + + public Duration transactionTimeout() + { + return this.txTimeout; + } + + @Override + public boolean safeToProcessInAnyState() + { + return false; + } + + @Override + public boolean equals( Object o ) + { + if ( this == o ) + { + return true; + } + if ( o == null || getClass() != o.getClass() ) + { + return false; + } + BeginMessage that = (BeginMessage) o; + return Objects.equals( meta, that.meta ) && Objects.equals( meta, that.meta ); + } + + @Override + public int hashCode() + { + return Objects.hash( meta ); + } + + @Override + public String toString() + { + return "BEGIN " + meta; + } + + public MapValue meta() + { + return meta; + } +} diff --git a/community/bolt/src/main/java/org/neo4j/bolt/v3/messaging/request/CommitMessage.java b/community/bolt/src/main/java/org/neo4j/bolt/v3/messaging/request/CommitMessage.java new file mode 100644 index 0000000000000..10bd4a62d4356 --- /dev/null +++ b/community/bolt/src/main/java/org/neo4j/bolt/v3/messaging/request/CommitMessage.java @@ -0,0 +1,45 @@ +/* + * Copyright (c) 2002-2018 "Neo4j," + * Neo4j Sweden AB [http://neo4j.com] + * + * This file is part of Neo4j. + * + * Neo4j is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ +package org.neo4j.bolt.v3.messaging.request; + +import org.neo4j.bolt.messaging.RequestMessage; + +public class CommitMessage implements RequestMessage +{ + public static final byte SIGNATURE = 0x12; + public static final CommitMessage COMMIT_MESSAGE = new CommitMessage(); + + private CommitMessage() + { + // left empty on purpose + } + + @Override + public boolean safeToProcessInAnyState() + { + return false; + } + + @Override + public String toString() + { + return "COMMIT"; + } +} diff --git a/community/bolt/src/main/java/org/neo4j/bolt/v3/messaging/HelloMessage.java b/community/bolt/src/main/java/org/neo4j/bolt/v3/messaging/request/HelloMessage.java similarity index 92% rename from community/bolt/src/main/java/org/neo4j/bolt/v3/messaging/HelloMessage.java rename to community/bolt/src/main/java/org/neo4j/bolt/v3/messaging/request/HelloMessage.java index 135aeb1944b9b..76a1ce28a5262 100644 --- a/community/bolt/src/main/java/org/neo4j/bolt/v3/messaging/HelloMessage.java +++ b/community/bolt/src/main/java/org/neo4j/bolt/v3/messaging/request/HelloMessage.java @@ -17,13 +17,15 @@ * You should have received a copy of the GNU General Public License * along with this program. If not, see . */ -package org.neo4j.bolt.v3.messaging; +package org.neo4j.bolt.v3.messaging.request; import java.util.Map; import java.util.Objects; import org.neo4j.bolt.v1.messaging.request.InitMessage; +import static java.util.Objects.requireNonNull; + public class HelloMessage extends InitMessage { public static final byte SIGNATURE = InitMessage.SIGNATURE; @@ -33,7 +35,7 @@ public class HelloMessage extends InitMessage public HelloMessage( Map meta ) { super( (String) meta.get( USER_AGENT ), meta ); - this.meta = meta; + this.meta = requireNonNull( meta ); } @Override diff --git a/community/bolt/src/main/java/org/neo4j/bolt/v3/messaging/request/RollbackMessage.java b/community/bolt/src/main/java/org/neo4j/bolt/v3/messaging/request/RollbackMessage.java new file mode 100644 index 0000000000000..a3e83b22440d1 --- /dev/null +++ b/community/bolt/src/main/java/org/neo4j/bolt/v3/messaging/request/RollbackMessage.java @@ -0,0 +1,46 @@ +/* + * Copyright (c) 2002-2018 "Neo4j," + * Neo4j Sweden AB [http://neo4j.com] + * + * This file is part of Neo4j. + * + * Neo4j is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ +package org.neo4j.bolt.v3.messaging.request; + +import org.neo4j.bolt.messaging.RequestMessage; + +public class RollbackMessage implements RequestMessage +{ + public static final byte SIGNATURE = 0x13; + + public static final RollbackMessage ROLLBACK_MESSAGE = new RollbackMessage(); + + private RollbackMessage() + { + // left empty on purpose + } + + @Override + public boolean safeToProcessInAnyState() + { + return false; + } + + @Override + public String toString() + { + return "ROLLBACK"; + } +} diff --git a/community/bolt/src/main/java/org/neo4j/bolt/v3/messaging/request/RunMessage.java b/community/bolt/src/main/java/org/neo4j/bolt/v3/messaging/request/RunMessage.java new file mode 100644 index 0000000000000..88179916b8766 --- /dev/null +++ b/community/bolt/src/main/java/org/neo4j/bolt/v3/messaging/request/RunMessage.java @@ -0,0 +1,132 @@ +/* + * Copyright (c) 2002-2018 "Neo4j," + * Neo4j Sweden AB [http://neo4j.com] + * + * This file is part of Neo4j. + * + * Neo4j is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ +package org.neo4j.bolt.v3.messaging.request; + +import java.time.Duration; +import java.util.Objects; + +import org.neo4j.bolt.messaging.BoltIOException; +import org.neo4j.bolt.messaging.RequestMessage; +import org.neo4j.bolt.v1.runtime.bookmarking.Bookmark; +import org.neo4j.bolt.v3.messaging.decoder.StatementMode; +import org.neo4j.values.virtual.MapValue; +import org.neo4j.values.virtual.VirtualValues; + +import static java.util.Objects.requireNonNull; +import static org.neo4j.bolt.v3.messaging.request.BeginMessage.parseBookmark; +import static org.neo4j.bolt.v3.messaging.request.BeginMessage.parseStatementMode; +import static org.neo4j.bolt.v3.messaging.request.BeginMessage.parseTransactionTimeout; + +public class RunMessage implements RequestMessage +{ + public static final byte SIGNATURE = 0x10; + + private final String statement; + private final MapValue params; + private final MapValue meta; + + private final StatementMode mode; + private final Bookmark bookmark; + private final Duration txTimeout; + + public RunMessage( String statement ) throws BoltIOException + { + this( statement, VirtualValues.EMPTY_MAP ); + } + + public RunMessage( String statement, MapValue params ) throws BoltIOException + { + this( statement, params, VirtualValues.EMPTY_MAP ); + } + + public RunMessage( String statement, MapValue params, MapValue meta ) throws BoltIOException + { + this.statement = requireNonNull( statement ); + this.params = requireNonNull( params ); + this.meta = requireNonNull( meta ); + + this.mode = parseStatementMode( meta ); + this.bookmark = parseBookmark( meta ); + this.txTimeout = parseTransactionTimeout( meta ); + } + + public String statement() + { + return statement; + } + + public MapValue params() + { + return params; + } + + public MapValue meta() + { + return meta; + } + + @Override + public boolean safeToProcessInAnyState() + { + return false; + } + + @Override + public boolean equals( Object o ) + { + if ( this == o ) + { + return true; + } + if ( o == null || getClass() != o.getClass() ) + { + return false; + } + RunMessage that = (RunMessage) o; + return Objects.equals( statement, that.statement ) && Objects.equals( params, that.params ) && Objects.equals( meta, that.meta ); + } + + @Override + public int hashCode() + { + return Objects.hash( statement, params, meta ); + } + + @Override + public String toString() + { + return "RUN " + statement + ' ' + params + ' ' + meta; + } + + public Bookmark bookmark() + { + return bookmark; + } + + public StatementMode mode() + { + return mode; + } + + public Duration transactionTimeout() + { + return txTimeout; + } +} diff --git a/community/bolt/src/main/java/org/neo4j/bolt/v3/runtime/DefunctState.java b/community/bolt/src/main/java/org/neo4j/bolt/v3/runtime/DefunctState.java new file mode 100644 index 0000000000000..33938da491669 --- /dev/null +++ b/community/bolt/src/main/java/org/neo4j/bolt/v3/runtime/DefunctState.java @@ -0,0 +1,43 @@ +/* + * Copyright (c) 2002-2018 "Neo4j," + * Neo4j Sweden AB [http://neo4j.com] + * + * This file is part of Neo4j. + * + * Neo4j is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ +package org.neo4j.bolt.v3.runtime; + +import org.neo4j.bolt.messaging.RequestMessage; +import org.neo4j.bolt.runtime.BoltConnectionFatality; +import org.neo4j.bolt.runtime.BoltStateMachineState; +import org.neo4j.bolt.runtime.StateMachineContext; + +/** + * This is a place holder for the states that require a failed state but the state machine shall be terminated. + */ +public class DefunctState implements BoltStateMachineState +{ + @Override + public BoltStateMachineState process( RequestMessage message, StateMachineContext context ) throws BoltConnectionFatality + { + return null; + } + + @Override + public String name() + { + return "DEFUNCT"; + } +} diff --git a/community/bolt/src/main/java/org/neo4j/bolt/v3/runtime/ExtraMetaDataConnectedState.java b/community/bolt/src/main/java/org/neo4j/bolt/v3/runtime/ExtraMetaDataConnectedState.java index 3dd59830d7990..0ab11b29e1227 100644 --- a/community/bolt/src/main/java/org/neo4j/bolt/v3/runtime/ExtraMetaDataConnectedState.java +++ b/community/bolt/src/main/java/org/neo4j/bolt/v3/runtime/ExtraMetaDataConnectedState.java @@ -24,7 +24,7 @@ import org.neo4j.bolt.runtime.BoltStateMachineState; import org.neo4j.bolt.runtime.StateMachineContext; import org.neo4j.bolt.v1.runtime.ConnectedState; -import org.neo4j.bolt.v3.messaging.HelloMessage; +import org.neo4j.bolt.v3.messaging.request.HelloMessage; import org.neo4j.values.storable.Values; /** diff --git a/community/bolt/src/main/java/org/neo4j/bolt/v3/runtime/FailSafeBoltStateMachineState.java b/community/bolt/src/main/java/org/neo4j/bolt/v3/runtime/FailSafeBoltStateMachineState.java new file mode 100644 index 0000000000000..096963b3e1f41 --- /dev/null +++ b/community/bolt/src/main/java/org/neo4j/bolt/v3/runtime/FailSafeBoltStateMachineState.java @@ -0,0 +1,58 @@ +/* + * Copyright (c) 2002-2018 "Neo4j," + * Neo4j Sweden AB [http://neo4j.com] + * + * This file is part of Neo4j. + * + * Neo4j is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ +package org.neo4j.bolt.v3.runtime; + +import org.neo4j.bolt.runtime.BoltConnectionFatality; +import org.neo4j.bolt.runtime.BoltStateMachineState; +import org.neo4j.bolt.runtime.StateMachineContext; +import org.neo4j.graphdb.security.AuthorizationExpiredException; + +public abstract class FailSafeBoltStateMachineState implements BoltStateMachineState +{ + protected BoltStateMachineState failedState; + + public void setFailedState( BoltStateMachineState failedState ) + { + this.failedState = failedState; + } + + BoltStateMachineState processMessage( StateMachineContext context, SuccessStateSupplier successStateSupplier ) throws BoltConnectionFatality + { + try + { + return successStateSupplier.processMessage(); + } + catch ( AuthorizationExpiredException e ) + { + context.handleFailure( e, true ); + return failedState; + } + catch ( Throwable t ) + { + context.handleFailure( t, false ); + return failedState; + } + } + + interface SuccessStateSupplier + { + BoltStateMachineState processMessage() throws Throwable; + } +} diff --git a/community/bolt/src/main/java/org/neo4j/bolt/v3/runtime/FailedState.java b/community/bolt/src/main/java/org/neo4j/bolt/v3/runtime/FailedState.java new file mode 100644 index 0000000000000..274dda9a2a438 --- /dev/null +++ b/community/bolt/src/main/java/org/neo4j/bolt/v3/runtime/FailedState.java @@ -0,0 +1,82 @@ +/* + * Copyright (c) 2002-2018 "Neo4j," + * Neo4j Sweden AB [http://neo4j.com] + * + * This file is part of Neo4j. + * + * Neo4j is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ +package org.neo4j.bolt.v3.runtime; + +import org.neo4j.bolt.messaging.RequestMessage; +import org.neo4j.bolt.runtime.BoltConnectionFatality; +import org.neo4j.bolt.runtime.BoltStateMachineState; +import org.neo4j.bolt.runtime.StateMachineContext; +import org.neo4j.bolt.v1.messaging.request.DiscardAllMessage; +import org.neo4j.bolt.v1.messaging.request.InterruptSignal; +import org.neo4j.bolt.v1.messaging.request.PullAllMessage; +import org.neo4j.bolt.v1.messaging.request.RunMessage; + +import static org.neo4j.util.Preconditions.checkState; + +/** + * The FAILED state occurs when a recoverable error is encountered. + * This might be something like a Cypher SyntaxError or + * ConstraintViolation. To exit the FAILED state, either a RESET + * or and ACK_FAILURE must be issued. All stream will be IGNORED + * until this is done. + */ +public class FailedState implements BoltStateMachineState +{ + private BoltStateMachineState interruptedState; + + @Override + public BoltStateMachineState process( RequestMessage message, StateMachineContext context ) throws BoltConnectionFatality + { + assertInitialized(); + if ( shouldIgnore( message ) ) + { + context.connectionState().markIgnored(); + return this; + } + if ( message instanceof InterruptSignal ) + { + return interruptedState; + } + return null; + } + + @Override + public String name() + { + return "FAILED"; + } + + public void setInterruptedState( BoltStateMachineState interruptedState ) + { + this.interruptedState = interruptedState; + } + + private void assertInitialized() + { + checkState( interruptedState != null, "Interrupted state not set" ); + } + + private static boolean shouldIgnore( RequestMessage message ) + { + return message instanceof RunMessage || + message instanceof PullAllMessage || + message instanceof DiscardAllMessage; + } +} diff --git a/community/bolt/src/main/java/org/neo4j/bolt/v3/runtime/ReadyState.java b/community/bolt/src/main/java/org/neo4j/bolt/v3/runtime/ReadyState.java new file mode 100644 index 0000000000000..a757b19b1811e --- /dev/null +++ b/community/bolt/src/main/java/org/neo4j/bolt/v3/runtime/ReadyState.java @@ -0,0 +1,126 @@ +/* + * Copyright (c) 2002-2018 "Neo4j," + * Neo4j Sweden AB [http://neo4j.com] + * + * This file is part of Neo4j. + * + * Neo4j is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ +package org.neo4j.bolt.v3.runtime; + +import org.neo4j.bolt.messaging.RequestMessage; +import org.neo4j.bolt.runtime.BoltConnectionFatality; +import org.neo4j.bolt.runtime.BoltStateMachineState; +import org.neo4j.bolt.runtime.StateMachineContext; +import org.neo4j.bolt.runtime.StatementMetadata; +import org.neo4j.bolt.runtime.StatementProcessor; +import org.neo4j.bolt.v1.messaging.request.InterruptSignal; +import org.neo4j.bolt.v3.messaging.request.BeginMessage; +import org.neo4j.bolt.v3.messaging.request.RunMessage; +import org.neo4j.internal.kernel.api.exceptions.KernelException; + +import org.neo4j.values.storable.Values; + +import static org.neo4j.util.Preconditions.checkState; +import static org.neo4j.values.storable.Values.stringArray; + +/** + * The READY state indicates that the connection is ready to accept a + * new RUN request. This is the "normal" state for a connection and + * becomes available after successful authorisation and when not + * executing another statement. It is this that ensures that statements + * must be executed in series and each must wait for the previous + * statement to complete. + */ +public class ReadyState extends FailSafeBoltStateMachineState +{ + private BoltStateMachineState streamingState; + private BoltStateMachineState interruptedState; + private BoltStateMachineState txReadyState; + + static final String FIELDS_KEY = "fields"; + static final String FIRST_RECORD_AVAILABLE_KEY = "result_available_after"; + private static final String TX_ID_KEY = "tx_id"; + + @Override + public BoltStateMachineState process( RequestMessage message, StateMachineContext context ) throws BoltConnectionFatality + { + assertInitialized(); + if ( message instanceof RunMessage ) + { + return processMessage( context, () -> processRunMessage( (RunMessage) message, context ) ); + } + if ( message instanceof BeginMessage ) + { + return processMessage( context, () -> processBeginMessage( (BeginMessage) message, context ) ); + } + if ( message instanceof InterruptSignal ) + { + return interruptedState; + } + return null; + } + + @Override + public String name() + { + return "READY"; + } + + public void setStreamingState( BoltStateMachineState streamingState ) + { + this.streamingState = streamingState; + } + + public void setInterruptedState( BoltStateMachineState interruptedState ) + { + this.interruptedState = interruptedState; + } + + public void setTransactionReadyState( BoltStateMachineState txReadyState ) + { + this.txReadyState = txReadyState; + } + + private BoltStateMachineState processRunMessage( RunMessage message, StateMachineContext context ) throws KernelException + { + long start = context.clock().millis(); + StatementProcessor statementProcessor = context.connectionState().getStatementProcessor(); + StatementMetadata statementMetadata = statementProcessor.run( message.statement(), message.params(), message.bookmark() ); + long end = context.clock().millis(); + + context.connectionState().onMetadata( FIELDS_KEY, stringArray( statementMetadata.fieldNames() ) ); + context.connectionState().onMetadata( FIRST_RECORD_AVAILABLE_KEY, Values.longValue( end - start ) ); + context.connectionState().onMetadata( TX_ID_KEY, Values.NO_VALUE ); //TODO return tx_id + + return streamingState; + } + + private BoltStateMachineState processBeginMessage( BeginMessage message, StateMachineContext context ) throws Exception + { + StatementProcessor statementProcessor = context.connectionState().getStatementProcessor(); + statementProcessor.beginTransaction( message.bookmark() ); + context.connectionState().onMetadata( TX_ID_KEY, Values.NO_VALUE ); // TODO + return txReadyState; + } + + private void assertInitialized() + { + checkState( streamingState != null, "Streaming state not set" ); + checkState( interruptedState != null, "Interrupted state not set" ); + checkState( failedState != null, "Failed state not set" ); + checkState( txReadyState != null, "TransactionReady state not set" ); + } + +} diff --git a/community/bolt/src/main/java/org/neo4j/bolt/v3/runtime/StreamingState.java b/community/bolt/src/main/java/org/neo4j/bolt/v3/runtime/StreamingState.java new file mode 100644 index 0000000000000..80d97c594651f --- /dev/null +++ b/community/bolt/src/main/java/org/neo4j/bolt/v3/runtime/StreamingState.java @@ -0,0 +1,100 @@ +/* + * Copyright (c) 2002-2018 "Neo4j," + * Neo4j Sweden AB [http://neo4j.com] + * + * This file is part of Neo4j. + * + * Neo4j is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ +package org.neo4j.bolt.v3.runtime; + +import org.neo4j.bolt.messaging.RequestMessage; +import org.neo4j.bolt.runtime.BoltConnectionFatality; +import org.neo4j.bolt.runtime.BoltStateMachineState; +import org.neo4j.bolt.runtime.StateMachineContext; +import org.neo4j.bolt.v1.messaging.request.DiscardAllMessage; +import org.neo4j.bolt.v1.messaging.request.InterruptSignal; +import org.neo4j.bolt.v1.messaging.request.PullAllMessage; + +import static org.neo4j.util.Preconditions.checkState; + +/** + * When STREAMING, a result is available as a stream of records. + * These must be PULLed or DISCARDed before any further statements + * can be executed. + */ +public class StreamingState extends FailSafeBoltStateMachineState +{ + private BoltStateMachineState readyState; + private BoltStateMachineState interruptedState; + + @Override + public BoltStateMachineState process( RequestMessage message, StateMachineContext context ) throws BoltConnectionFatality + { + assertInitialized(); + if ( message instanceof PullAllMessage ) + { + return processPullAllMessage( context ); + } + if ( message instanceof DiscardAllMessage ) + { + return processDiscardAllMessage( context ); + } + if ( message instanceof InterruptSignal ) + { + return interruptedState; + } + return null; + } + + @Override + public String name() + { + return "STREAMING"; + } + + public void setReadyState( BoltStateMachineState readyState ) + { + this.readyState = readyState; + } + + public void setInterruptedState( BoltStateMachineState interruptedState ) + { + this.interruptedState = interruptedState; + } + + private BoltStateMachineState processPullAllMessage( StateMachineContext context ) throws BoltConnectionFatality + { + return processMessage( context, () -> processStreamResultMessage( true, context ) ); + } + + private BoltStateMachineState processDiscardAllMessage( StateMachineContext context ) throws BoltConnectionFatality + { + return processMessage( context, () -> processStreamResultMessage( false, context ) ); + } + + private BoltStateMachineState processStreamResultMessage( boolean pull, StateMachineContext context ) throws Throwable + { + context.connectionState().getStatementProcessor().streamResult( + recordStream -> context.connectionState().getResponseHandler().onRecords( recordStream, pull ) ); + return readyState; + } + + private void assertInitialized() + { + checkState( readyState != null, "Ready state not set" ); + checkState( interruptedState != null, "Interrupted state not set" ); + checkState( failedState != null, "Failed state not set" ); + } +} diff --git a/community/bolt/src/main/java/org/neo4j/bolt/v3/runtime/TransactionReadyState.java b/community/bolt/src/main/java/org/neo4j/bolt/v3/runtime/TransactionReadyState.java new file mode 100644 index 0000000000000..738cdbb9bf415 --- /dev/null +++ b/community/bolt/src/main/java/org/neo4j/bolt/v3/runtime/TransactionReadyState.java @@ -0,0 +1,125 @@ +/* + * Copyright (c) 2002-2018 "Neo4j," + * Neo4j Sweden AB [http://neo4j.com] + * + * This file is part of Neo4j. + * + * Neo4j is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ +package org.neo4j.bolt.v3.runtime; + +import org.neo4j.bolt.messaging.RequestMessage; +import org.neo4j.bolt.runtime.BoltConnectionFatality; +import org.neo4j.bolt.runtime.BoltStateMachineState; +import org.neo4j.bolt.runtime.StateMachineContext; +import org.neo4j.bolt.runtime.StatementMetadata; +import org.neo4j.bolt.runtime.StatementProcessor; +import org.neo4j.bolt.v1.messaging.request.InterruptSignal; +import org.neo4j.bolt.v1.runtime.bookmarking.Bookmark; +import org.neo4j.bolt.v3.messaging.request.CommitMessage; +import org.neo4j.bolt.v3.messaging.request.RollbackMessage; +import org.neo4j.bolt.v3.messaging.request.RunMessage; +import org.neo4j.internal.kernel.api.exceptions.KernelException; +import org.neo4j.values.storable.Values; + +import static org.neo4j.bolt.v3.runtime.ReadyState.FIELDS_KEY; +import static org.neo4j.bolt.v3.runtime.ReadyState.FIRST_RECORD_AVAILABLE_KEY; +import static org.neo4j.util.Preconditions.checkState; +import static org.neo4j.values.storable.Values.stringArray; + +public class TransactionReadyState extends FailSafeBoltStateMachineState +{ + private BoltStateMachineState streaming; + private BoltStateMachineState interruptedState; + private BoltStateMachineState readyState; + + @Override + public BoltStateMachineState process( RequestMessage message, StateMachineContext context ) throws BoltConnectionFatality + { + assertInitialized(); + if ( message instanceof RunMessage ) + { + return processMessage( context, () -> processRunMessage( (RunMessage) message, context ) ); + } + if ( message instanceof CommitMessage ) + { + return processMessage( context, () -> processCommitMessage( context ) ); + } + if ( message instanceof RollbackMessage ) + { + return processMessage( context, () -> processRollbackMessage( context ) ); + } + if ( message instanceof InterruptSignal ) + { + return interruptedState; + } + return null; + } + + @Override + public String name() + { + return "TX_READY"; + } + + public void setTransactionStreamingState( BoltStateMachineState streamingState ) + { + this.streaming = streamingState; + } + + public void setInterruptedState( BoltStateMachineState interruptedState ) + { + this.interruptedState = interruptedState; + } + + public void setReadyState( BoltStateMachineState readyState ) + { + this.readyState = readyState; + } + + private BoltStateMachineState processRunMessage( RunMessage message, StateMachineContext context ) throws KernelException + { + long start = context.clock().millis(); + StatementProcessor statementProcessor = context.connectionState().getStatementProcessor(); + StatementMetadata statementMetadata = statementProcessor.run( message.statement(), message.params() ); + long end = context.clock().millis(); + + context.connectionState().onMetadata( FIELDS_KEY, stringArray( statementMetadata.fieldNames() ) ); + context.connectionState().onMetadata( FIRST_RECORD_AVAILABLE_KEY, Values.longValue( end - start ) ); + return streaming; + } + + private BoltStateMachineState processCommitMessage( StateMachineContext context ) throws Exception + { + StatementProcessor statementProcessor = context.connectionState().getStatementProcessor(); + Bookmark bookmark = statementProcessor.commitTransaction(); + bookmark.attachTo( context.connectionState() ); + return readyState; + } + + private BoltStateMachineState processRollbackMessage( StateMachineContext context ) throws Exception + { + StatementProcessor statementProcessor = context.connectionState().getStatementProcessor(); + statementProcessor.rollbackTransaction(); + return readyState; + } + + private void assertInitialized() + { + checkState( streaming != null, "Streaming state not set" ); + checkState( interruptedState != null, "Interrupted state not set" ); + checkState( readyState != null, "Ready state not set" ); + checkState( failedState != null, "Failed state not set" ); + } +} diff --git a/community/bolt/src/test/java/org/neo4j/bolt/logging/BoltMessageLoggerImplTest.java b/community/bolt/src/test/java/org/neo4j/bolt/logging/BoltMessageLoggerImplTest.java index cf01f79c7194e..cc3a7c6539da0 100644 --- a/community/bolt/src/test/java/org/neo4j/bolt/logging/BoltMessageLoggerImplTest.java +++ b/community/bolt/src/test/java/org/neo4j/bolt/logging/BoltMessageLoggerImplTest.java @@ -124,7 +124,7 @@ public void logAckFailure() public void logInit() { // when - boltMessageLogger.logUserAgent( "userAgent" ); + boltMessageLogger.logInit( "userAgent" ); // then verify( boltMessageLog ).info( REMOTE_ADDRESS, CORRELATION_ID, "C INIT userAgent" ); diff --git a/community/bolt/src/test/java/org/neo4j/bolt/testing/BoltMatchers.java b/community/bolt/src/test/java/org/neo4j/bolt/testing/BoltMatchers.java index b2719892c7db7..ae69701117b66 100644 --- a/community/bolt/src/test/java/org/neo4j/bolt/testing/BoltMatchers.java +++ b/community/bolt/src/test/java/org/neo4j/bolt/testing/BoltMatchers.java @@ -75,6 +75,11 @@ public void describeTo( Description description ) }; } + public static Matcher succeededWithMetadata( final String key, final String value ) + { + return succeededWithMetadata( key, stringValue( value ) ); + } + public static Matcher succeededWithMetadata( final String key, final AnyValue value ) { return new BaseMatcher() diff --git a/community/bolt/src/test/java/org/neo4j/bolt/transport/DefaultBoltProtocolFactoryTest.java b/community/bolt/src/test/java/org/neo4j/bolt/transport/DefaultBoltProtocolFactoryTest.java index 752d891477bf5..68a7178005329 100644 --- a/community/bolt/src/test/java/org/neo4j/bolt/transport/DefaultBoltProtocolFactoryTest.java +++ b/community/bolt/src/test/java/org/neo4j/bolt/transport/DefaultBoltProtocolFactoryTest.java @@ -30,9 +30,10 @@ import org.neo4j.bolt.runtime.BoltConnection; import org.neo4j.bolt.runtime.BoltConnectionFactory; import org.neo4j.bolt.runtime.BoltStateMachine; -import org.neo4j.bolt.v1.BoltProtocolV1; import org.neo4j.bolt.runtime.BoltStateMachineFactory; +import org.neo4j.bolt.v1.BoltProtocolV1; import org.neo4j.bolt.v2.BoltProtocolV2; +import org.neo4j.bolt.v3.BoltProtocolV3; import org.neo4j.kernel.impl.logging.NullLogService; import static org.junit.Assert.assertEquals; @@ -44,12 +45,12 @@ import static org.mockito.Mockito.verify; import static org.mockito.Mockito.when; -public class DefaultBoltProtocolFactoryTest +class DefaultBoltProtocolFactoryTest { private static final String CONNECTOR = "default"; @Test - public void shouldCreateNothingForUnknownProtocolVersion() + void shouldCreateNothingForUnknownProtocolVersion() { int protocolVersion = 42; BoltChannel channel = mock( BoltChannel.class ); @@ -64,8 +65,8 @@ public void shouldCreateNothingForUnknownProtocolVersion() } @ParameterizedTest( name = "V{0}" ) - @ValueSource( longs = {BoltProtocolV1.VERSION, BoltProtocolV2.VERSION} ) - public void shouldCreateBoltProtocol( long protocolVersion ) + @ValueSource( longs = {BoltProtocolV1.VERSION, BoltProtocolV2.VERSION, BoltProtocolV3.VERSION} ) + void shouldCreateBoltProtocol( long protocolVersion ) throws Throwable { EmbeddedChannel channel = new EmbeddedChannel(); BoltChannel boltChannel = BoltChannel.open( CONNECTOR, channel, NullBoltMessageLogger.getInstance() ); diff --git a/community/bolt/src/test/java/org/neo4j/bolt/v1/BoltProtocolV1Test.java b/community/bolt/src/test/java/org/neo4j/bolt/v1/BoltProtocolV1Test.java index a86e66f5f9ddd..26b31eb2b55f8 100644 --- a/community/bolt/src/test/java/org/neo4j/bolt/v1/BoltProtocolV1Test.java +++ b/community/bolt/src/test/java/org/neo4j/bolt/v1/BoltProtocolV1Test.java @@ -59,7 +59,7 @@ public void cleanup() } @Test - public void shouldInstallChannelHandlersInCorrectOrder() + public void shouldInstallChannelHandlersInCorrectOrder() throws Throwable { // Given BoltChannel boltChannel = newBoltChannel( channel ); diff --git a/community/bolt/src/test/java/org/neo4j/bolt/v1/runtime/BoltStateMachineFactoryImplTest.java b/community/bolt/src/test/java/org/neo4j/bolt/v1/runtime/BoltStateMachineFactoryImplTest.java index 341a0ca722f1e..b7fca634938f1 100644 --- a/community/bolt/src/test/java/org/neo4j/bolt/v1/runtime/BoltStateMachineFactoryImplTest.java +++ b/community/bolt/src/test/java/org/neo4j/bolt/v1/runtime/BoltStateMachineFactoryImplTest.java @@ -44,9 +44,10 @@ import org.neo4j.udc.UsageData; import static org.hamcrest.CoreMatchers.instanceOf; +import static org.hamcrest.CoreMatchers.startsWith; import static org.junit.Assert.assertNotNull; -import static org.junit.Assert.assertNull; import static org.junit.Assert.assertThat; +import static org.junit.jupiter.api.Assertions.assertThrows; import static org.mockito.Mockito.mock; import static org.mockito.Mockito.when; @@ -57,7 +58,7 @@ class BoltStateMachineFactoryImplTest @ParameterizedTest( name = "V{0}" ) @ValueSource( longs = {BoltProtocolV1.VERSION, BoltProtocolV2.VERSION} ) - void shouldCreateBoltStateMachines( long protocolVersion ) + void shouldCreateBoltStateMachines( long protocolVersion ) throws Throwable { BoltStateMachineFactoryImpl factory = newBoltFactory(); @@ -68,7 +69,7 @@ void shouldCreateBoltStateMachines( long protocolVersion ) } @Test - void shouldCreateBoltStateMachinesV3() + void shouldCreateBoltStateMachinesV3() throws Throwable { BoltStateMachineFactoryImpl factory = newBoltFactory(); @@ -80,13 +81,12 @@ void shouldCreateBoltStateMachinesV3() @ParameterizedTest( name = "V{0}" ) @ValueSource( longs = {999, -1} ) - void shouldReturnNullIfVersionIsUnknown( long protocolVersion ) + void shouldThrowExceptionIfVersionIsUnknown( long protocolVersion ) throws Throwable { BoltStateMachineFactoryImpl factory = newBoltFactory(); - BoltStateMachine boltStateMachine = factory.newStateMachine( protocolVersion, CHANNEL ); - - assertNull( boltStateMachine ); + IllegalArgumentException error = assertThrows( IllegalArgumentException.class, () -> factory.newStateMachine( protocolVersion, CHANNEL ) ); + assertThat( error.getMessage(), startsWith( "Failed to create a state machine for protocol version" ) ); } private static BoltStateMachineFactoryImpl newBoltFactory() diff --git a/community/bolt/src/test/java/org/neo4j/bolt/v1/runtime/TransactionStateMachineTest.java b/community/bolt/src/test/java/org/neo4j/bolt/v1/runtime/TransactionStateMachineTest.java index 1a1829387e145..8bcb68acecdb3 100644 --- a/community/bolt/src/test/java/org/neo4j/bolt/v1/runtime/TransactionStateMachineTest.java +++ b/community/bolt/src/test/java/org/neo4j/bolt/v1/runtime/TransactionStateMachineTest.java @@ -72,7 +72,7 @@ void createMocks() void shouldTransitionToExplicitTransactionOnBegin() throws Exception { assertEquals( TransactionStateMachine.State.EXPLICIT_TRANSACTION, - TransactionStateMachine.State.AUTO_COMMIT.beginTransaction( null, mutableState, stateMachineSPI ) ); + TransactionStateMachine.State.AUTO_COMMIT.beginTransaction( mutableState, stateMachineSPI, null ) ); } @Test @@ -93,7 +93,7 @@ void shouldTransitionToAutoCommitOnRollback() throws Exception void shouldThrowOnBeginInExplicitTransaction() throws Exception { QueryExecutionKernelException e = assertThrows( QueryExecutionKernelException.class, () -> - TransactionStateMachine.State.EXPLICIT_TRANSACTION.beginTransaction( null, mutableState, stateMachineSPI ) ); + TransactionStateMachine.State.EXPLICIT_TRANSACTION.beginTransaction( mutableState, stateMachineSPI, null ) ); assertEquals( "Nested transactions are not supported.", e.getMessage() ); } diff --git a/community/bolt/src/test/java/org/neo4j/bolt/v1/transport/integration/TransportTestUtil.java b/community/bolt/src/test/java/org/neo4j/bolt/v1/transport/integration/TransportTestUtil.java index 1d1bfda2c843d..4c15a71759102 100644 --- a/community/bolt/src/test/java/org/neo4j/bolt/v1/transport/integration/TransportTestUtil.java +++ b/community/bolt/src/test/java/org/neo4j/bolt/v1/transport/integration/TransportTestUtil.java @@ -43,11 +43,18 @@ public class TransportTestUtil { - private final Neo4jPack neo4jPack; + protected final Neo4jPack neo4jPack; + private final MessageEncoder messageEncoder; public TransportTestUtil( Neo4jPack neo4jPack ) + { + this( neo4jPack, new MessageEncoderV1() ); + } + + public TransportTestUtil( Neo4jPack neo4jPack, MessageEncoder messageEncoder ) { this.neo4jPack = neo4jPack; + this.messageEncoder = messageEncoder; } public Neo4jPack getNeo4jPack() @@ -70,7 +77,7 @@ public byte[] chunk( int chunkSize, RequestMessage... messages ) throws IOExcept byte[][] serializedMessages = new byte[messages.length][]; for ( int i = 0; i < messages.length; i++ ) { - serializedMessages[i] = serialize( neo4jPack, messages[i] ); + serializedMessages[i] = messageEncoder.encode( neo4jPack, messages[i] ); } return chunk( chunkSize, serializedMessages ); } @@ -270,4 +277,24 @@ public void describeTo( Description description ) } }; } + + public interface MessageEncoder + { + byte[] encode( Neo4jPack neo4jPack, RequestMessage... messages ) throws IOException; + byte[] encode( Neo4jPack neo4jPack, ResponseMessage... messages ) throws IOException; + } + + private static class MessageEncoderV1 implements MessageEncoder + { + public byte[] encode( Neo4jPack neo4jPack, RequestMessage... messages ) throws IOException + { + return serialize( neo4jPack, messages ); + } + + @Override + public byte[] encode( Neo4jPack neo4jPack, ResponseMessage... messages ) throws IOException + { + return serialize( neo4jPack, messages ); + } + } } diff --git a/community/bolt/src/test/java/org/neo4j/bolt/v3/BoltProtocolV3Test.java b/community/bolt/src/test/java/org/neo4j/bolt/v3/BoltProtocolV3Test.java index 27ce9da95375a..c55b7c8a44c5d 100644 --- a/community/bolt/src/test/java/org/neo4j/bolt/v3/BoltProtocolV3Test.java +++ b/community/bolt/src/test/java/org/neo4j/bolt/v3/BoltProtocolV3Test.java @@ -37,7 +37,7 @@ class BoltProtocolV3Test { @Test - void shouldCreatePackForBoltV3() + void shouldCreatePackForBoltV3() throws Throwable { BoltProtocolV3 protocolV3 = new BoltProtocolV3( mock( BoltChannel.class ), ( ch, st ) -> mock( BoltConnection.class ), mock( BoltStateMachineFactory.class ), @@ -47,7 +47,7 @@ void shouldCreatePackForBoltV3() } @Test - void shouldVersionReturnBoltV3() + void shouldVersionReturnBoltV3() throws Throwable { BoltProtocolV3 protocolV3 = new BoltProtocolV3( mock( BoltChannel.class ), ( ch, st ) -> mock( BoltConnection.class ), mock( BoltStateMachineFactory.class ), @@ -57,7 +57,7 @@ void shouldVersionReturnBoltV3() } @Test - void shouldCreateMessageReaderForBoltV3() + void shouldCreateMessageReaderForBoltV3() throws Throwable { BoltProtocolV3 protocolV3 = new BoltProtocolV3( mock( BoltChannel.class ), ( ch, st ) -> mock( BoltConnection.class ), mock( BoltStateMachineFactory.class ), diff --git a/community/bolt/src/test/java/org/neo4j/bolt/v3/messaging/BoltProtocolV3ComponentFactory.java b/community/bolt/src/test/java/org/neo4j/bolt/v3/messaging/BoltProtocolV3ComponentFactory.java index bf7cbf6dcb8cb..71318e73e3c4b 100644 --- a/community/bolt/src/test/java/org/neo4j/bolt/v3/messaging/BoltProtocolV3ComponentFactory.java +++ b/community/bolt/src/test/java/org/neo4j/bolt/v3/messaging/BoltProtocolV3ComponentFactory.java @@ -26,22 +26,25 @@ import org.neo4j.bolt.messaging.BoltResponseMessageWriter; import org.neo4j.bolt.messaging.Neo4jPack; import org.neo4j.bolt.messaging.RequestMessage; +import org.neo4j.bolt.messaging.ResponseMessage; import org.neo4j.bolt.runtime.BoltStateMachine; import org.neo4j.bolt.runtime.SynchronousBoltConnection; import org.neo4j.bolt.v1.messaging.BoltRequestMessageWriter; import org.neo4j.bolt.v1.messaging.RecordingByteChannel; import org.neo4j.bolt.v1.packstream.BufferedChannelOutput; +import org.neo4j.bolt.v1.transport.integration.TransportTestUtil; import org.neo4j.bolt.v2.messaging.Neo4jPackV2; import org.neo4j.kernel.impl.logging.NullLogService; import static org.mockito.Mockito.mock; +import static org.neo4j.bolt.v1.messaging.util.MessageMatchers.serialize; /** * A helper factory to generate boltV3 component in tests */ public class BoltProtocolV3ComponentFactory { - public static Neo4jPack neo4jPack() + public static Neo4jPack newNeo4jPack() { return new Neo4jPackV2(); } @@ -72,4 +75,21 @@ public static byte[] encode( Neo4jPack neo4jPack, RequestMessage... messages ) t return rawData.getBytes(); } + public static TransportTestUtil.MessageEncoder newMessageEncoder() + { + return new TransportTestUtil.MessageEncoder() + { + @Override + public byte[] encode( Neo4jPack neo4jPack, RequestMessage... messages ) throws IOException + { + return BoltProtocolV3ComponentFactory.encode( neo4jPack, messages ); + } + + @Override + public byte[] encode( Neo4jPack neo4jPack, ResponseMessage... messages ) throws IOException + { + return serialize( neo4jPack, messages ); + } + }; + } } diff --git a/community/bolt/src/test/java/org/neo4j/bolt/v3/messaging/BoltRequestMessageReaderV3Test.java b/community/bolt/src/test/java/org/neo4j/bolt/v3/messaging/BoltRequestMessageReaderV3Test.java index aebb2a94315cf..f55013c0f90a5 100644 --- a/community/bolt/src/test/java/org/neo4j/bolt/v3/messaging/BoltRequestMessageReaderV3Test.java +++ b/community/bolt/src/test/java/org/neo4j/bolt/v3/messaging/BoltRequestMessageReaderV3Test.java @@ -19,55 +19,90 @@ */ package org.neo4j.bolt.v3.messaging; -import org.junit.jupiter.api.Test; +import org.junit.jupiter.params.ParameterizedTest; +import org.junit.jupiter.params.provider.MethodSource; + +import java.util.stream.Stream; import org.neo4j.bolt.messaging.BoltIOException; import org.neo4j.bolt.messaging.BoltRequestMessageReader; import org.neo4j.bolt.messaging.Neo4jPack; import org.neo4j.bolt.messaging.RequestMessage; import org.neo4j.bolt.runtime.BoltStateMachine; +import org.neo4j.bolt.v1.messaging.request.AckFailureMessage; +import org.neo4j.bolt.v1.messaging.request.DiscardAllMessage; import org.neo4j.bolt.v1.messaging.request.InitMessage; +import org.neo4j.bolt.v1.messaging.request.PullAllMessage; +import org.neo4j.bolt.v1.messaging.request.ResetMessage; import org.neo4j.bolt.v1.packstream.PackedInputArray; +import org.neo4j.bolt.v3.messaging.request.BeginMessage; +import org.neo4j.bolt.v3.messaging.request.HelloMessage; +import org.neo4j.bolt.v3.messaging.request.RunMessage; -import static org.hamcrest.CoreMatchers.startsWith; -import static org.junit.Assert.assertThat; import static org.junit.jupiter.api.Assertions.assertThrows; import static org.mockito.ArgumentMatchers.any; import static org.mockito.ArgumentMatchers.eq; import static org.mockito.Mockito.mock; import static org.mockito.Mockito.verify; import static org.neo4j.bolt.v3.messaging.BoltProtocolV3ComponentFactory.encode; -import static org.neo4j.bolt.v3.messaging.BoltProtocolV3ComponentFactory.neo4jPack; +import static org.neo4j.bolt.v3.messaging.BoltProtocolV3ComponentFactory.newNeo4jPack; +import static org.neo4j.bolt.v3.messaging.BoltProtocolV3ComponentFactory.requestMessageReader; +import static org.neo4j.bolt.v3.messaging.request.CommitMessage.COMMIT_MESSAGE; +import static org.neo4j.bolt.v3.messaging.request.RollbackMessage.ROLLBACK_MESSAGE; import static org.neo4j.helpers.collection.MapUtil.map; +import static org.neo4j.values.virtual.VirtualValues.EMPTY_MAP; class BoltRequestMessageReaderV3Test { - @Test - void shouldDecodeHelloMessage() throws Exception + @ParameterizedTest + @MethodSource( "boltV3Messages" ) + void shouldDecodeV3Messages( RequestMessage message ) throws Exception { - testMessageDecoding( new HelloMessage( map( "user_agent", "My driver", "one", 1L, "two", 2L ) ) ); + testMessageDecoding( message ); } - @Test - void shouldNotDecodeInitMessage() throws Exception + @ParameterizedTest + @MethodSource( "boltV3UnsupportedMessages" ) + void shouldNotDecodeUnsupportedMessages( RequestMessage message ) throws Exception { - BoltIOException exception = - assertThrows( BoltIOException.class, () -> testMessageDecoding( new InitMessage( "My driver", map( "one", 1L, "two", 2L ) ) ) ); - assertThat( exception.getMessage(), startsWith( "Unable to read message type." ) ); + assertThrows( Exception.class, () -> testMessageDecoding( message ) ); } private static void testMessageDecoding( RequestMessage message ) throws Exception { - Neo4jPack neo4jPack = neo4jPack(); + Neo4jPack neo4jPack = newNeo4jPack(); BoltStateMachine stateMachine = mock( BoltStateMachine.class ); - BoltRequestMessageReader reader = BoltProtocolV3ComponentFactory.requestMessageReader( stateMachine ); + BoltRequestMessageReader reader = requestMessageReader( stateMachine ); - PackedInputArray innput = new PackedInputArray( encode( neo4jPack, message ) ); - Neo4jPack.Unpacker unpacker = neo4jPack.newUnpacker( innput ); + PackedInputArray input = new PackedInputArray( encode( neo4jPack, message ) ); + Neo4jPack.Unpacker unpacker = neo4jPack.newUnpacker( input ); reader.read( unpacker ); verify( stateMachine ).process( eq( message ), any() ); } + + private static Stream boltV3Messages() throws BoltIOException + { + return Stream.of( + new HelloMessage( map( "user_agent", "My driver", "one", 1L, "two", 2L ) ), + new RunMessage( "RETURN 1", EMPTY_MAP, EMPTY_MAP ), + DiscardAllMessage.INSTANCE, + PullAllMessage.INSTANCE, + new BeginMessage(), + COMMIT_MESSAGE, + ROLLBACK_MESSAGE, + ResetMessage.INSTANCE ); + } + + private static Stream boltV3UnsupportedMessages() + { + return Stream.of( + new InitMessage( "My driver", map( "one", 1L, "two", 2L ) ), + AckFailureMessage.INSTANCE, + new org.neo4j.bolt.v1.messaging.request.RunMessage( "RETURN 1" ) + ); + } + } diff --git a/community/bolt/src/test/java/org/neo4j/bolt/v3/messaging/BoltRequestMessageWriterV3.java b/community/bolt/src/test/java/org/neo4j/bolt/v3/messaging/BoltRequestMessageWriterV3.java index c2ff2e3607e62..df16ff34f174c 100644 --- a/community/bolt/src/test/java/org/neo4j/bolt/v3/messaging/BoltRequestMessageWriterV3.java +++ b/community/bolt/src/test/java/org/neo4j/bolt/v3/messaging/BoltRequestMessageWriterV3.java @@ -25,6 +25,11 @@ import org.neo4j.bolt.messaging.Neo4jPack; import org.neo4j.bolt.messaging.RequestMessage; import org.neo4j.bolt.v1.messaging.BoltRequestMessageWriter; +import org.neo4j.bolt.v3.messaging.request.BeginMessage; +import org.neo4j.bolt.v3.messaging.request.CommitMessage; +import org.neo4j.bolt.v3.messaging.request.HelloMessage; +import org.neo4j.bolt.v3.messaging.request.RollbackMessage; +import org.neo4j.bolt.v3.messaging.request.RunMessage; import org.neo4j.kernel.impl.util.ValueUtils; /** @@ -44,7 +49,78 @@ public BoltRequestMessageWriter write( RequestMessage message ) throws IOExcepti { writeHello( (HelloMessage) message ); } - return super.write( message ); + else if ( message instanceof BeginMessage ) + { + writeBegin( (BeginMessage) message ); + } + else if ( message instanceof CommitMessage ) + { + writeCommit(); + } + else if ( message instanceof RollbackMessage ) + { + writeRollback(); + } + else if ( message instanceof RunMessage ) + { + writeRun( (RunMessage) message ); + } + else + { + super.write( message ); + } + return this; + } + + private void writeRun( RunMessage message ) + { + try + { + packer.packStructHeader( 0, RunMessage.SIGNATURE ); + packer.pack( message.statement() ); + packer.pack( message.params() ); + packer.pack( message.meta() ); + } + catch ( IOException e ) + { + throw new UncheckedIOException( e ); + } + } + + private void writeRollback() + { + writeSignatureOnlyMessage( RollbackMessage.SIGNATURE ); + } + + private void writeCommit() + { + writeSignatureOnlyMessage( CommitMessage.SIGNATURE ); + } + + private void writeBegin( BeginMessage message ) + { + try + { + packer.packStructHeader( 0, BeginMessage.SIGNATURE ); + packer.pack( message.meta() ); + } + catch ( IOException e ) + { + throw new UncheckedIOException( e ); + } + + } + + private void writeSignatureOnlyMessage( byte signature ) + { + try + { + packer.packStructHeader( 0, signature ); + } + catch ( IOException e ) + { + throw new UncheckedIOException( e ); + } } private void writeHello( HelloMessage message ) diff --git a/community/bolt/src/test/java/org/neo4j/bolt/v3/messaging/decoder/BeginMessageDecoderTest.java b/community/bolt/src/test/java/org/neo4j/bolt/v3/messaging/decoder/BeginMessageDecoderTest.java new file mode 100644 index 0000000000000..c039e9bb5a5ca --- /dev/null +++ b/community/bolt/src/test/java/org/neo4j/bolt/v3/messaging/decoder/BeginMessageDecoderTest.java @@ -0,0 +1,60 @@ +/* + * Copyright (c) 2002-2018 "Neo4j," + * Neo4j Sweden AB [http://neo4j.com] + * + * This file is part of Neo4j. + * + * Neo4j is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ +package org.neo4j.bolt.v3.messaging.decoder; + +import org.junit.jupiter.api.Test; + +import org.neo4j.bolt.messaging.RequestMessageDecoder; +import org.neo4j.bolt.runtime.BoltResponseHandler; +import org.neo4j.bolt.v3.messaging.request.BeginMessage; +import org.neo4j.values.AnyValue; +import org.neo4j.values.virtual.VirtualValues; + +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.mockito.Mockito.mock; +import static org.neo4j.bolt.v3.messaging.decoder.HelloMessageDecoderTest.assertOriginalMessageEqualsToDecoded; +import static org.neo4j.values.storable.Values.longValue; +import static org.neo4j.values.storable.Values.stringValue; + +class BeginMessageDecoderTest +{ + private final BoltResponseHandler responseHandler = mock( BoltResponseHandler.class ); + private final RequestMessageDecoder decoder = new BeginMessageDecoder( responseHandler ); + + @Test + void shouldReturnCorrectSignature() + { + assertEquals( BeginMessage.SIGNATURE, decoder.signature() ); + } + + @Test + void shouldReturnConnectResponseHandler() + { + assertEquals( responseHandler, decoder.responseHandler() ); + } + + @Test + void shouldDecodeBeginMessage() throws Exception + { + BeginMessage originalMessage = + new BeginMessage( VirtualValues.map( new String[]{"mode", "tx_timeout"}, new AnyValue[]{stringValue( "R" ), longValue( 10000 )} ) ); + assertOriginalMessageEqualsToDecoded( originalMessage, decoder ); + } +} diff --git a/community/bolt/src/test/java/org/neo4j/bolt/v3/messaging/decoder/CommitMessageDecoderTest.java b/community/bolt/src/test/java/org/neo4j/bolt/v3/messaging/decoder/CommitMessageDecoderTest.java new file mode 100644 index 0000000000000..f1a7fb6cf6b8e --- /dev/null +++ b/community/bolt/src/test/java/org/neo4j/bolt/v3/messaging/decoder/CommitMessageDecoderTest.java @@ -0,0 +1,55 @@ +/* + * Copyright (c) 2002-2018 "Neo4j," + * Neo4j Sweden AB [http://neo4j.com] + * + * This file is part of Neo4j. + * + * Neo4j is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ +package org.neo4j.bolt.v3.messaging.decoder; + +import org.junit.jupiter.api.Test; + +import org.neo4j.bolt.messaging.RequestMessageDecoder; +import org.neo4j.bolt.runtime.BoltResponseHandler; +import org.neo4j.bolt.v3.messaging.request.CommitMessage; + +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.mockito.Mockito.mock; +import static org.neo4j.bolt.v3.messaging.decoder.HelloMessageDecoderTest.assertOriginalMessageEqualsToDecoded; +import static org.neo4j.bolt.v3.messaging.request.CommitMessage.COMMIT_MESSAGE; + +class CommitMessageDecoderTest +{ + private final BoltResponseHandler responseHandler = mock( BoltResponseHandler.class ); + private final RequestMessageDecoder decoder = new CommitMessageDecoder( responseHandler ); + + @Test + void shouldReturnCorrectSignature() + { + assertEquals( CommitMessage.SIGNATURE, decoder.signature() ); + } + + @Test + void shouldReturnConnectResponseHandler() + { + assertEquals( responseHandler, decoder.responseHandler() ); + } + + @Test + void shouldDecodeBeginMessage() throws Exception + { + assertOriginalMessageEqualsToDecoded( COMMIT_MESSAGE, decoder ); + } +} diff --git a/community/bolt/src/test/java/org/neo4j/bolt/v3/messaging/HelloMessageDecoderTest.java b/community/bolt/src/test/java/org/neo4j/bolt/v3/messaging/decoder/HelloMessageDecoderTest.java similarity index 77% rename from community/bolt/src/test/java/org/neo4j/bolt/v3/messaging/HelloMessageDecoderTest.java rename to community/bolt/src/test/java/org/neo4j/bolt/v3/messaging/decoder/HelloMessageDecoderTest.java index c9ef4ec41d0c5..2b88e6bb42fc8 100644 --- a/community/bolt/src/test/java/org/neo4j/bolt/v3/messaging/HelloMessageDecoderTest.java +++ b/community/bolt/src/test/java/org/neo4j/bolt/v3/messaging/decoder/HelloMessageDecoderTest.java @@ -17,28 +17,27 @@ * You should have received a copy of the GNU General Public License * along with this program. If not, see . */ -package org.neo4j.bolt.v3.messaging; +package org.neo4j.bolt.v3.messaging.decoder; import org.junit.jupiter.api.Test; -import org.neo4j.bolt.logging.BoltMessageLogger; import org.neo4j.bolt.messaging.Neo4jPack; import org.neo4j.bolt.messaging.RequestMessage; import org.neo4j.bolt.messaging.RequestMessageDecoder; import org.neo4j.bolt.runtime.BoltResponseHandler; import org.neo4j.bolt.v1.packstream.PackedInputArray; +import org.neo4j.bolt.v3.messaging.request.HelloMessage; import static org.junit.jupiter.api.Assertions.assertEquals; import static org.mockito.Mockito.mock; import static org.neo4j.bolt.v3.messaging.BoltProtocolV3ComponentFactory.encode; -import static org.neo4j.bolt.v3.messaging.BoltProtocolV3ComponentFactory.neo4jPack; +import static org.neo4j.bolt.v3.messaging.BoltProtocolV3ComponentFactory.newNeo4jPack; import static org.neo4j.helpers.collection.MapUtil.map; class HelloMessageDecoderTest { private final BoltResponseHandler responseHandler = mock( BoltResponseHandler.class ); - private final BoltMessageLogger messageLogger = mock( BoltMessageLogger.class ); - private final RequestMessageDecoder decoder = new HelloMessageDecoder( responseHandler, messageLogger ); + private final RequestMessageDecoder decoder = new HelloMessageDecoder( responseHandler ); @Test void shouldReturnCorrectSignature() @@ -53,13 +52,18 @@ void shouldReturnConnectResponseHandler() } @Test - void shouldDecodeAckFailure() throws Exception + void shouldDecodeHelloMessage() throws Exception { - Neo4jPack neo4jPack = neo4jPack(); HelloMessage originalMessage = new HelloMessage( map( "user_agent", "My Driver", "user", "neo4j", "password", "secret" ) ); + assertOriginalMessageEqualsToDecoded( originalMessage, decoder ); + } + + static void assertOriginalMessageEqualsToDecoded( RequestMessage originalMessage, RequestMessageDecoder decoder ) throws Exception + { + Neo4jPack neo4jPack = newNeo4jPack(); - PackedInputArray innput = new PackedInputArray( encode( neo4jPack, originalMessage ) ); - Neo4jPack.Unpacker unpacker = neo4jPack.newUnpacker( innput ); + PackedInputArray input = new PackedInputArray( encode( neo4jPack, originalMessage ) ); + Neo4jPack.Unpacker unpacker = neo4jPack.newUnpacker( input ); // these two steps are executed before decoding in order to select a correct decoder unpacker.unpackStructHeader(); @@ -68,4 +72,5 @@ void shouldDecodeAckFailure() throws Exception RequestMessage deserializedMessage = decoder.decode( unpacker ); assertEquals( originalMessage, deserializedMessage ); } + } diff --git a/community/bolt/src/test/java/org/neo4j/bolt/v3/messaging/decoder/RollbackMessageDecoderTest.java b/community/bolt/src/test/java/org/neo4j/bolt/v3/messaging/decoder/RollbackMessageDecoderTest.java new file mode 100644 index 0000000000000..a3fe787559c31 --- /dev/null +++ b/community/bolt/src/test/java/org/neo4j/bolt/v3/messaging/decoder/RollbackMessageDecoderTest.java @@ -0,0 +1,55 @@ +/* + * Copyright (c) 2002-2018 "Neo4j," + * Neo4j Sweden AB [http://neo4j.com] + * + * This file is part of Neo4j. + * + * Neo4j is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ +package org.neo4j.bolt.v3.messaging.decoder; + +import org.junit.jupiter.api.Test; + +import org.neo4j.bolt.messaging.RequestMessageDecoder; +import org.neo4j.bolt.runtime.BoltResponseHandler; +import org.neo4j.bolt.v3.messaging.request.RollbackMessage; + +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.mockito.Mockito.mock; +import static org.neo4j.bolt.v3.messaging.decoder.HelloMessageDecoderTest.assertOriginalMessageEqualsToDecoded; +import static org.neo4j.bolt.v3.messaging.request.RollbackMessage.ROLLBACK_MESSAGE; + +class RollbackMessageDecoderTest +{ + private final BoltResponseHandler responseHandler = mock( BoltResponseHandler.class ); + private final RequestMessageDecoder decoder = new RollbackMessageDecoder( responseHandler ); + + @Test + void shouldReturnCorrectSignature() + { + assertEquals( RollbackMessage.SIGNATURE, decoder.signature() ); + } + + @Test + void shouldReturnConnectResponseHandler() + { + assertEquals( responseHandler, decoder.responseHandler() ); + } + + @Test + void shouldDecodeBeginMessage() throws Exception + { + assertOriginalMessageEqualsToDecoded( ROLLBACK_MESSAGE, decoder ); + } +} diff --git a/community/bolt/src/test/java/org/neo4j/bolt/v3/messaging/decoder/RunMessageDecoderTest.java b/community/bolt/src/test/java/org/neo4j/bolt/v3/messaging/decoder/RunMessageDecoderTest.java new file mode 100644 index 0000000000000..9ccfc024c62b9 --- /dev/null +++ b/community/bolt/src/test/java/org/neo4j/bolt/v3/messaging/decoder/RunMessageDecoderTest.java @@ -0,0 +1,55 @@ +/* + * Copyright (c) 2002-2018 "Neo4j," + * Neo4j Sweden AB [http://neo4j.com] + * + * This file is part of Neo4j. + * + * Neo4j is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ +package org.neo4j.bolt.v3.messaging.decoder; + +import org.junit.jupiter.api.Test; + +import org.neo4j.bolt.messaging.RequestMessageDecoder; +import org.neo4j.bolt.runtime.BoltResponseHandler; +import org.neo4j.bolt.v3.messaging.request.RunMessage; + +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.mockito.Mockito.mock; +import static org.neo4j.bolt.v3.messaging.decoder.HelloMessageDecoderTest.assertOriginalMessageEqualsToDecoded; + +class RunMessageDecoderTest +{ + private final BoltResponseHandler responseHandler = mock( BoltResponseHandler.class ); + private final RequestMessageDecoder decoder = new RunMessageDecoder( responseHandler ); + + @Test + void shouldReturnCorrectSignature() + { + assertEquals( RunMessage.SIGNATURE, decoder.signature() ); + } + + @Test + void shouldReturnConnectResponseHandler() + { + assertEquals( responseHandler, decoder.responseHandler() ); + } + + @Test + void shouldDecodeBeginMessage() throws Exception + { + RunMessage originalMessage = new RunMessage( "A new V3 run messsage" ); + assertOriginalMessageEqualsToDecoded( originalMessage, decoder ); + } +} diff --git a/community/bolt/src/test/java/org/neo4j/bolt/v3/runtime/ExtraMetaDataConnectedStateTest.java b/community/bolt/src/test/java/org/neo4j/bolt/v3/runtime/ExtraMetaDataConnectedStateTest.java index 2ed3982a6d1b9..ae5f0af2214ad 100644 --- a/community/bolt/src/test/java/org/neo4j/bolt/v3/runtime/ExtraMetaDataConnectedStateTest.java +++ b/community/bolt/src/test/java/org/neo4j/bolt/v3/runtime/ExtraMetaDataConnectedStateTest.java @@ -29,7 +29,7 @@ import org.neo4j.bolt.runtime.StateMachineContext; import org.neo4j.bolt.security.auth.AuthenticationResult; import org.neo4j.bolt.v1.runtime.ConnectedState; -import org.neo4j.bolt.v3.messaging.HelloMessage; +import org.neo4j.bolt.v3.messaging.request.HelloMessage; import org.neo4j.values.storable.StringValue; import static org.junit.jupiter.api.Assertions.assertEquals; diff --git a/community/community-it/bolt-it/pom.xml b/community/community-it/bolt-it/pom.xml index fbdabade9791c..3dae40b134fe9 100644 --- a/community/community-it/bolt-it/pom.xml +++ b/community/community-it/bolt-it/pom.xml @@ -118,5 +118,9 @@ commons-codec commons-codec + + org.junit.jupiter + junit-jupiter-params + diff --git a/community/community-it/bolt-it/src/test/java/org/neo4j/bolt/v2/transport/integration/BoltV2TransportIT.java b/community/community-it/bolt-it/src/test/java/org/neo4j/bolt/v2/transport/integration/BoltV2TransportIT.java index 57e1d8755dde6..c4f4049e7f88b 100644 --- a/community/community-it/bolt-it/src/test/java/org/neo4j/bolt/v2/transport/integration/BoltV2TransportIT.java +++ b/community/community-it/bolt-it/src/test/java/org/neo4j/bolt/v2/transport/integration/BoltV2TransportIT.java @@ -279,7 +279,6 @@ private void testSendingOfBoltV2Value( T value ) throws Exc PullAllMessage.INSTANCE ) ); assertThat( connection, util.eventuallyReceives( - msgSuccess(), msgSuccess(), msgRecord( eqRecord( equalTo( longValue( 42 ) ) ) ), msgSuccess() ) ); @@ -294,7 +293,6 @@ private void testReceivingOfBoltV2Value( String query, T ex PullAllMessage.INSTANCE ) ); assertThat( connection, util.eventuallyReceives( - msgSuccess(), msgSuccess(), msgRecord( eqRecord( equalTo( expectedValue ) ) ), msgSuccess() ) ); @@ -309,7 +307,6 @@ private void testSendingAndReceivingOfBoltV2Value( T value PullAllMessage.INSTANCE ) ); assertThat( connection, util.eventuallyReceives( - msgSuccess(), msgSuccess(), msgRecord( eqRecord( equalTo( value ) ) ), msgSuccess() ) ); @@ -322,5 +319,6 @@ private void negotiateBoltV2() throws Exception .send( util.chunk( new InitMessage( USER_AGENT, emptyMap() ) ) ); assertThat( connection, eventuallyReceives( new byte[]{0, 0, 0, 2} ) ); + assertThat( connection, util.eventuallyReceives( msgSuccess() ) ); } } diff --git a/community/community-it/bolt-it/src/test/java/org/neo4j/bolt/v3/runtime/integration/BoltConnectionV3IT.java b/community/community-it/bolt-it/src/test/java/org/neo4j/bolt/v3/runtime/integration/BoltConnectionV3IT.java deleted file mode 100644 index f583a4dbd0a70..0000000000000 --- a/community/community-it/bolt-it/src/test/java/org/neo4j/bolt/v3/runtime/integration/BoltConnectionV3IT.java +++ /dev/null @@ -1,715 +0,0 @@ -/* - * Copyright (c) 2002-2018 "Neo4j," - * Neo4j Sweden AB [http://neo4j.com] - * - * This file is part of Neo4j. - * - * Neo4j is free software: you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation, either version 3 of the License, or - * (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with this program. If not, see . - */ -package org.neo4j.bolt.v3.runtime.integration; - -import org.hamcrest.CoreMatchers; -import org.junit.Before; -import org.junit.Rule; -import org.junit.Test; - -import java.util.concurrent.CountDownLatch; -import java.util.concurrent.TimeUnit; -import java.util.concurrent.atomic.AtomicReference; - -import org.neo4j.bolt.BoltChannel; -import org.neo4j.bolt.runtime.BoltConnectionFatality; -import org.neo4j.bolt.runtime.BoltResponseHandler; -import org.neo4j.bolt.runtime.BoltResult; -import org.neo4j.bolt.runtime.BoltStateMachine; -import org.neo4j.bolt.runtime.Neo4jError; -import org.neo4j.bolt.testing.BoltResponseRecorder; -import org.neo4j.bolt.testing.NullResponseHandler; -import org.neo4j.bolt.testing.RecordedBoltResponse; -import org.neo4j.bolt.v1.messaging.BoltResponseMessage; -import org.neo4j.bolt.v1.messaging.request.AckFailureMessage; -import org.neo4j.bolt.v1.messaging.request.DiscardAllMessage; -import org.neo4j.bolt.v1.messaging.request.InitMessage; -import org.neo4j.bolt.v1.messaging.request.PullAllMessage; -import org.neo4j.bolt.v1.messaging.request.ResetMessage; -import org.neo4j.bolt.v1.messaging.request.RunMessage; -import org.neo4j.bolt.v1.runtime.BoltStateMachineV1; -import org.neo4j.bolt.v1.runtime.integration.SessionRule; -import org.neo4j.bolt.v3.BoltProtocolV3; -import org.neo4j.bolt.v3.messaging.HelloMessage; -import org.neo4j.cypher.result.QueryResult.Record; -import org.neo4j.helpers.collection.MapUtil; -import org.neo4j.kernel.api.exceptions.Status; -import org.neo4j.kernel.impl.util.ValueUtils; -import org.neo4j.values.AnyValue; -import org.neo4j.values.storable.LongValue; -import org.neo4j.values.virtual.MapValue; -import org.neo4j.values.virtual.VirtualValues; - -import static java.util.Collections.emptyMap; -import static org.hamcrest.CoreMatchers.equalTo; -import static org.hamcrest.MatcherAssert.assertThat; -import static org.junit.Assert.assertEquals; -import static org.junit.Assert.assertFalse; -import static org.junit.Assert.assertTrue; -import static org.junit.jupiter.api.Assertions.assertThrows; -import static org.mockito.Mockito.mock; -import static org.mockito.Mockito.when; -import static org.neo4j.bolt.testing.BoltMatchers.failedWithStatus; -import static org.neo4j.bolt.testing.BoltMatchers.succeeded; -import static org.neo4j.bolt.testing.BoltMatchers.verifyKillsConnection; -import static org.neo4j.bolt.testing.NullResponseHandler.nullResponseHandler; -import static org.neo4j.bolt.v1.messaging.BoltResponseMessage.IGNORED; -import static org.neo4j.bolt.v1.messaging.BoltResponseMessage.SUCCESS; -import static org.neo4j.values.storable.Values.longValue; -import static org.neo4j.values.storable.Values.stringValue; - -@SuppressWarnings( "unchecked" ) -public class BoltConnectionV3IT -{ - private static final MapValue EMPTY_PARAMS = VirtualValues.EMPTY_MAP; - private static final String USER_AGENT = "BoltConnectionIT/0.0"; - private static final BoltChannel boltChannel = mock( BoltChannel.class ); - - @Before - public void setup() - { - when( boltChannel.id() ).thenReturn( "conn-v3-test-boltchannel-id" ); - } - - @Rule - public SessionRule env = new SessionRule(); - - @Test - public void shouldCloseConnectionOnInit() throws Throwable - { - // Given - BoltStateMachine machine = newStateMachine(); - - // when - NullResponseHandler handler = nullResponseHandler(); - - assertThrows( BoltConnectionFatality.class, () -> machine.process( new InitMessage( USER_AGENT, emptyMap() ), handler ) ); - // then - verifyKillsConnection( () -> machine.process( ResetMessage.INSTANCE, handler ) ); - } - - @Test - public void shouldCloseConnectionResetBeforeHello() throws Throwable - { - // Given - BoltStateMachine machine = newStateMachine(); - - // when - BoltResponseRecorder recorder = new BoltResponseRecorder(); - verifyKillsConnection( () -> machine.process( ResetMessage.INSTANCE, recorder ) ); - - // then - assertThat( recorder.nextResponse(), failedWithStatus( Status.Request.Invalid ) ); - } - - @Test - public void shouldCloseConnectionOnRunBeforeHello() throws Throwable - { - // Given - BoltStateMachine machine = newStateMachine(); - - // when - BoltResponseRecorder recorder = new BoltResponseRecorder(); - verifyKillsConnection( () -> machine.process( new RunMessage( "RETURN 1", map() ), recorder ) ); - - // then - assertThat( recorder.nextResponse(), failedWithStatus( Status.Request.Invalid ) ); - } - - @Test - public void shouldCloseConnectionOnDiscardAllBeforeHello() throws Throwable - { - // Given - BoltStateMachine machine = newStateMachine(); - - // when - BoltResponseRecorder recorder = new BoltResponseRecorder(); - verifyKillsConnection( () -> machine.process( DiscardAllMessage.INSTANCE, recorder ) ); - - // then - assertThat( recorder.nextResponse(), failedWithStatus( Status.Request.Invalid ) ); - } - - @Test - public void shouldCloseConnectionOnPullAllBeforeHello() throws Throwable - { - // Given - BoltStateMachine machine = newStateMachine(); - - // when - BoltResponseRecorder recorder = new BoltResponseRecorder(); - verifyKillsConnection( () -> machine.process( PullAllMessage.INSTANCE, recorder ) ); - - // then - assertThat( recorder.nextResponse(), failedWithStatus( Status.Request.Invalid ) ); - } - - @Test - public void shouldExecuteStatement() throws Throwable - { - // Given - BoltStateMachine machine = newStateMachine(); - machine.process( newHelloMessage(), nullResponseHandler() ); - - // When - BoltResponseRecorder recorder = new BoltResponseRecorder(); - machine.process( new RunMessage( "CREATE (n {k:'k'}) RETURN n.k", EMPTY_PARAMS ), recorder ); - - // Then - assertThat( recorder.nextResponse(), succeeded() ); - - // When - recorder.reset(); - machine.process( PullAllMessage.INSTANCE, recorder ); - - // Then - recorder.nextResponse().assertRecord( 0, stringValue( "k" ) ); - //assertThat( pulling.next(), streamContaining( StreamMatchers.eqRecord( equalTo( "k" ) ) ) ); - } - - @Test - public void shouldSucceedOn__run__pullAll__run() throws Throwable - { - // Given - BoltStateMachine machine = newStateMachine(); - machine.process( newHelloMessage(), nullResponseHandler() ); - - // And Given that I've ran and pulled one stream - machine.process( new RunMessage( "RETURN 1", EMPTY_PARAMS ), nullResponseHandler() ); - machine.process( PullAllMessage.INSTANCE, nullResponseHandler() ); - - // When I run a new statement - BoltResponseRecorder recorder = new BoltResponseRecorder(); - machine.process( new RunMessage( "RETURN 2", EMPTY_PARAMS ), recorder ); - - // Then - assertThat( recorder.nextResponse(), succeeded() ); - } - - @Test - public void shouldSucceedOn__run__discardAll__run() throws Throwable - { - // Given - BoltStateMachine machine = newStateMachine(); - machine.process( newHelloMessage(), nullResponseHandler() ); - - // And Given that I've ran and pulled one stream - machine.process( new RunMessage( "RETURN 1", EMPTY_PARAMS ), nullResponseHandler() ); - machine.process( DiscardAllMessage.INSTANCE, nullResponseHandler() ); - - // When I run a new statement - BoltResponseRecorder recorder = new BoltResponseRecorder(); - machine.process( new RunMessage( "RETURN 2", EMPTY_PARAMS ), recorder ); - - // Then - assertThat( recorder.nextResponse(), succeeded() ); - } - - @Test - public void shouldSucceedOn__run_BEGIN__pullAll__run_COMMIT__pullALL__run_COMMIT() throws Throwable - { - // Given - BoltStateMachine machine = newStateMachine(); - machine.process( newHelloMessage(), nullResponseHandler() ); - - // And Given that I've ran and pulled one stream - BoltResponseRecorder recorder = new BoltResponseRecorder(); - machine.process( new RunMessage( "BEGIN", EMPTY_PARAMS ), recorder ); - machine.process( PullAllMessage.INSTANCE, recorder ); - machine.process( new RunMessage( "COMMIT", EMPTY_PARAMS ), recorder ); - machine.process( PullAllMessage.INSTANCE, recorder ); - assertThat( recorder.nextResponse(), succeeded() ); - assertThat( recorder.nextResponse(), succeeded() ); - assertThat( recorder.nextResponse(), succeeded() ); - assertThat( recorder.nextResponse(), succeeded() ); - - // When I run a new statement - recorder.reset(); - machine.process( new RunMessage( "BEGIN", EMPTY_PARAMS ), recorder ); - - // Then - assertThat( recorder.nextResponse(), succeeded() ); - } - - @Test - public void shouldFailOn__run__run() throws Throwable - { - // Given - BoltStateMachine machine = newStateMachine(); - machine.process( newHelloMessage(), nullResponseHandler() ); - - // And Given that I've ran one statement - machine.process( new RunMessage( "RETURN 1", EMPTY_PARAMS ), nullResponseHandler() ); - - // When I run a new statement, before consuming the stream - BoltResponseRecorder recorder = new BoltResponseRecorder(); - verifyKillsConnection( () -> machine.process( new RunMessage( "RETURN 1", EMPTY_PARAMS ), recorder ) ); - - // Then - assertThat( recorder.nextResponse(), failedWithStatus( Status.Request.Invalid ) ); - } - - @Test - public void shouldFailOn__pullAll__pullAll() throws Throwable - { - // Given - BoltStateMachine machine = newStateMachine(); - machine.process( newHelloMessage(), nullResponseHandler() ); - - // And Given that I've ran and pulled one stream - machine.process( new RunMessage( "RETURN 1", EMPTY_PARAMS ), nullResponseHandler() ); - machine.process( PullAllMessage.INSTANCE, nullResponseHandler() ); - - // Then further attempts to PULL should be treated as protocol violations - BoltResponseRecorder recorder = new BoltResponseRecorder(); - verifyKillsConnection( () -> machine.process( PullAllMessage.INSTANCE, recorder ) ); - - // Then - assertThat( recorder.nextResponse(), failedWithStatus( Status.Request.Invalid ) ); - } - - @Test - public void shouldFailOn__pullAll__discardAll() throws Throwable - { - // Given - BoltStateMachine machine = newStateMachine(); - machine.process( newHelloMessage(), nullResponseHandler() ); - - // And Given that I've ran and pulled one stream - machine.process( new RunMessage( "RETURN 1", EMPTY_PARAMS ), nullResponseHandler() ); - machine.process( PullAllMessage.INSTANCE, nullResponseHandler() ); - - // When I attempt to pull more items from the stream - BoltResponseRecorder recorder = new BoltResponseRecorder(); - verifyKillsConnection( () -> machine.process( DiscardAllMessage.INSTANCE, recorder ) ); - - // Then - assertThat( recorder.nextResponse(), failedWithStatus( Status.Request.Invalid ) ); - } - - @Test - public void shouldFailOn__discardAll__discardAll() throws Throwable - { - // Given - BoltStateMachine machine = newStateMachine(); - machine.process( newHelloMessage(), nullResponseHandler() ); - - // And Given that I've ran and pulled one stream - machine.process( new RunMessage( "RETURN 1", EMPTY_PARAMS ), nullResponseHandler() ); - machine.process( DiscardAllMessage.INSTANCE, nullResponseHandler() ); - - // When I attempt to pull more items from the stream - BoltResponseRecorder recorder = new BoltResponseRecorder(); - verifyKillsConnection( () -> machine.process( DiscardAllMessage.INSTANCE, recorder ) ); - - // Then - assertThat( recorder.nextResponse(), failedWithStatus( Status.Request.Invalid ) ); - } - - @Test - public void shouldFailOn__discardAll__pullAll() throws Throwable - { - // Given - BoltStateMachine machine = newStateMachine(); - machine.process( newHelloMessage(), nullResponseHandler() ); - - // And Given that I've ran and pulled one stream - machine.process( new RunMessage( "RETURN 1", EMPTY_PARAMS ), nullResponseHandler() ); - machine.process( DiscardAllMessage.INSTANCE, nullResponseHandler() ); - - // When I attempt to pull more items from the stream - BoltResponseRecorder recorder = new BoltResponseRecorder(); - verifyKillsConnection( () -> machine.process( PullAllMessage.INSTANCE, recorder ) ); - - // Then - assertThat( recorder.nextResponse(), failedWithStatus( Status.Request.Invalid ) ); - } - - @Test - public void shouldHandleImplicitCommitFailure() throws Throwable - { - // Given - BoltStateMachine machine = newStateMachine(); - machine.process( newHelloMessage(), nullResponseHandler() ); - machine.process( new RunMessage( "CREATE (n:Victim)-[:REL]->()", EMPTY_PARAMS ), nullResponseHandler() ); - machine.process( DiscardAllMessage.INSTANCE, nullResponseHandler() ); - - // When I perform an action that will fail on commit - BoltResponseRecorder recorder = new BoltResponseRecorder(); - machine.process( new RunMessage( "MATCH (n:Victim) DELETE n", EMPTY_PARAMS ), recorder ); - // Then the statement running should have succeeded - assertThat( recorder.nextResponse(), succeeded() ); - - recorder.reset(); - machine.process( DiscardAllMessage.INSTANCE, recorder ); - - // But the stop should have failed, since it implicitly triggers commit and thus triggers a failure - assertThat( recorder.nextResponse(), failedWithStatus( Status.Schema.ConstraintValidationFailed ) ); - //assertThat( discarding.next(), failedWith( Status.Schema.ConstraintValidationFailed ) ); - } - - @Test - public void shouldAllowUserControlledRollbackOnExplicitTxFailure() throws Throwable - { - // Given whenever en explicit transaction has a failure, - // it is more natural for drivers to see the failure, acknowledge it - // and send a `ROLLBACK`, because that means that all failures in the - // transaction, be they client-local or inside neo, can be handled the - // same way by a driver. - BoltStateMachine machine = newStateMachine(); - machine.process( newHelloMessage(), nullResponseHandler() ); - - machine.process( new RunMessage( "BEGIN", EMPTY_PARAMS ), nullResponseHandler() ); - machine.process( DiscardAllMessage.INSTANCE, nullResponseHandler() ); - machine.process( new RunMessage( "CREATE (n:Victim)-[:REL]->()", EMPTY_PARAMS ), nullResponseHandler() ); - machine.process( DiscardAllMessage.INSTANCE, nullResponseHandler() ); - - // When I perform an action that will fail - BoltResponseRecorder recorder = new BoltResponseRecorder(); - machine.process( new RunMessage( "this is not valid syntax", EMPTY_PARAMS ), recorder ); - - // Then I should see a failure - assertThat( recorder.nextResponse(), failedWithStatus( Status.Statement.SyntaxError ) ); - - // And when I acknowledge that failure, and roll back the transaction - recorder.reset(); - machine.process( AckFailureMessage.INSTANCE, recorder ); - machine.process( new RunMessage( "ROLLBACK", EMPTY_PARAMS ), recorder ); - - // Then both operations should succeed - assertThat( recorder.nextResponse(), succeeded() ); - assertThat( recorder.nextResponse(), succeeded() ); - } - - @Test - public void shouldHandleFailureDuringResultPublishing() throws Throwable - { - // Given - BoltStateMachine machine = newStateMachine(); - machine.process( newHelloMessage(), nullResponseHandler() ); - - final CountDownLatch pullAllCallbackCalled = new CountDownLatch( 1 ); - final AtomicReference error = new AtomicReference<>(); - - // When something fails while publishing the result stream - machine.process( new RunMessage( "RETURN 1", EMPTY_PARAMS ), nullResponseHandler() ); - machine.process( PullAllMessage.INSTANCE, new BoltResponseHandler() - { - @Override - public void onRecords( BoltResult result, boolean pull ) - { - throw new RuntimeException( "Ooopsies!" ); - } - - @Override - public void onMetadata( String key, AnyValue value ) - { - } - - @Override - public void markFailed( Neo4jError err ) - { - error.set( err ); - pullAllCallbackCalled.countDown(); - } - - @Override - public void markIgnored() - { - } - - @Override - public void onFinish() - { - } - } ); - - // Then - assertTrue( pullAllCallbackCalled.await( 30, TimeUnit.SECONDS ) ); - final Neo4jError err = error.get(); - assertThat( err.status(), equalTo( Status.General.UnknownError ) ); - assertThat( err.message(), CoreMatchers.containsString( "Ooopsies!" ) ); - } - - @Test - public void shouldBeAbleToCleanlyRunMultipleSessionsInSingleThread() throws Throwable - { - // Given - BoltStateMachine firstMachine = newStateMachine(); - firstMachine.process( newHelloMessage(), null ); - BoltStateMachine secondMachine = newStateMachine(); - secondMachine.process( newHelloMessage(), null ); - - // And given I've started a transaction in one session - runAndPull( firstMachine, "BEGIN" ); - - // When I issue a statement in a separate session - Object[] stream = runAndPull( secondMachine, "CREATE (a:Person) RETURN id(a)" ); - long id = ((LongValue) ((Record) stream[0]).fields()[0]).value(); - - // And when I roll back that first session transaction - runAndPull( firstMachine, "ROLLBACK" ); - - // Then the two should not have interfered with each other - stream = runAndPull( secondMachine, "MATCH (a:Person) WHERE id(a) = " + id + " RETURN COUNT(*)" ); - assertThat( ((Record) stream[0]).fields()[0], equalTo( longValue( 1L ) ) ); - } - - @Test - public void shouldSupportUsingPeriodicCommitInSession() throws Exception - { - // Given - BoltStateMachine machine = newStateMachine(); - machine.process( newHelloMessage(), nullResponseHandler() ); - MapValue params = map( "csvFileUrl", createLocalIrisData( machine ) ); - long txIdBeforeQuery = env.lastClosedTxId(); - long batch = 40; - - // When - Object[] result = runAndPull( machine, - "USING PERIODIC COMMIT " + batch + "\n" + "LOAD CSV WITH HEADERS FROM {csvFileUrl} AS l\n" + "MATCH (c:Class {name: l.class_name})\n" + - "CREATE (s:Sample {sepal_length: l.sepal_length, sepal_width: l.sepal_width, " + - "petal_length: l.petal_length, petal_width: l.petal_width})\n" + "CREATE (c)<-[:HAS_CLASS]-(s)\n" + "RETURN count(*) AS c", params ); - - // Then - assertThat( result.length, equalTo( 1 ) ); - Record record = (Record) result[0]; - - AnyValue[] fields = record.fields(); - assertThat( fields.length, equalTo( 1 ) ); - assertThat( fields[0], equalTo( longValue( 150L ) ) ); - - /* - * 7 tokens have been created for - * 'Sample' label - * 'HAS_CLASS' relationship type - * 'name', 'sepal_length', 'sepal_width', 'petal_length', and 'petal_width' property keys - * - * Note that the token id for the label 'Class' has been created in `createLocalIrisData(...)` so it shouldn't1 - * be counted again here - */ - long tokensCommits = 7; - long commits = (IRIS_DATA.split( "\n" ).length - 1 /* header */) / batch; - long txId = env.lastClosedTxId(); - assertEquals( tokensCommits + commits + txIdBeforeQuery, txId ); - } - - @Test - public void shouldNotSupportUsingPeriodicCommitInTransaction() throws Exception - { - // Given - BoltStateMachine machine = newStateMachine(); - machine.process( newHelloMessage(), nullResponseHandler() ); - MapValue params = map( "csvFileUrl", createLocalIrisData( machine ) ); - runAndPull( machine, "BEGIN" ); - - // When - BoltResponseRecorder recorder = new BoltResponseRecorder(); - machine.process( new RunMessage( - "USING PERIODIC COMMIT 40\n" + "LOAD CSV WITH HEADERS FROM {csvFileUrl} AS l\n" + "MATCH (c:Class {name: l.class_name})\n" + - "CREATE (s:Sample {sepal_length: l.sepal_length, sepal_width: l.sepal_width, petal_length: l" + - ".petal_length, petal_width: l.petal_width})\n" + "CREATE (c)<-[:HAS_CLASS]-(s)\n" + "RETURN count(*) AS c", params ), recorder ); - - // Then - assertThat( recorder.nextResponse(), failedWithStatus( Status.Statement.SemanticError ) ); - // "Executing queries that use periodic commit in an open transaction is not possible." - } - - @Test - public void shouldCloseTransactionOnCommit() throws Exception - { - // Given - BoltStateMachine machine = newStateMachine(); - machine.process( newHelloMessage(), nullResponseHandler() ); - - runAndPull( machine, "BEGIN" ); - runAndPull( machine, "RETURN 1" ); - runAndPull( machine, "COMMIT" ); - - assertFalse( hasTransaction( machine ) ); - } - - @Test - public void shouldCloseTransactionEvenIfCommitFails() throws Exception - { - // Given - BoltStateMachine machine = newStateMachine(); - machine.process( newHelloMessage(), nullResponseHandler() ); - - runAndPull( machine, "BEGIN" ); - runAndPull( machine, "X", map(), IGNORED ); - machine.process( AckFailureMessage.INSTANCE, nullResponseHandler() ); - runAndPull( machine, "COMMIT", map(), IGNORED ); - machine.process( AckFailureMessage.INSTANCE, nullResponseHandler() ); - - assertFalse( hasTransaction( machine ) ); - } - - @Test - public void shouldCloseTransactionOnRollback() throws Exception - { - // Given - BoltStateMachine machine = newStateMachine(); - machine.process( newHelloMessage(), nullResponseHandler() ); - - runAndPull( machine, "BEGIN" ); - runAndPull( machine, "RETURN 1" ); - runAndPull( machine, "ROLLBACK" ); - - assertFalse( hasTransaction( machine ) ); - } - - @Test - public void shouldCloseTransactionOnRollbackAfterFailure() throws Exception - { - // Given - BoltStateMachine machine = newStateMachine(); - machine.process( newHelloMessage(), nullResponseHandler() ); - - runAndPull( machine, "BEGIN" ); - runAndPull( machine, "X", map(), IGNORED ); - machine.process( AckFailureMessage.INSTANCE, nullResponseHandler() ); - runAndPull( machine, "ROLLBACK" ); - - assertFalse( hasTransaction( machine ) ); - } - - @Test - public void shouldAllowNewTransactionAfterFailure() throws Throwable - { - // Given - BoltStateMachine machine = newStateMachine(); - machine.process( newHelloMessage(), nullResponseHandler() ); - - // And given I've started a transaction that failed - runAndPull( machine, "BEGIN" ); - machine.process( new RunMessage( "invalid", EMPTY_PARAMS ), nullResponseHandler() ); - machine.process( ResetMessage.INSTANCE, nullResponseHandler() ); - - // When - runAndPull( machine, "BEGIN" ); - Object[] stream = runAndPull( machine, "RETURN 1" ); - - // Then - assertThat( ((Record) stream[0]).fields()[0], equalTo( longValue( 1L ) ) ); - } - - private static boolean hasTransaction( BoltStateMachine machine ) - { - return ((BoltStateMachineV1) machine).statementProcessor().hasTransaction(); - } - - private String createLocalIrisData( BoltStateMachine machine ) throws Exception - { - for ( String className : IRIS_CLASS_NAMES ) - { - MapValue params = map( "className", className ); - runAndPull( machine, "CREATE (c:Class {name: {className}}) RETURN c", params ); - } - - return env.putTmpFile( "iris", ".csv", IRIS_DATA ).toExternalForm(); - } - - private Object[] runAndPull( BoltStateMachine machine, String statement ) throws Exception - { - return runAndPull( machine, statement, EMPTY_PARAMS, SUCCESS ); - } - - private Record[] runAndPull( BoltStateMachine machine, String statement, MapValue params ) throws Exception - { - return runAndPull( machine, statement, params, SUCCESS ); - } - - private Record[] runAndPull( BoltStateMachine machine, String statement, MapValue params, BoltResponseMessage expectedResponse ) throws Exception - { - BoltResponseRecorder recorder = new BoltResponseRecorder(); - machine.process( new RunMessage( statement, params ), nullResponseHandler() ); - machine.process( PullAllMessage.INSTANCE, recorder ); - RecordedBoltResponse response = recorder.nextResponse(); - assertEquals( expectedResponse, response.message() ); - return response.records(); - } - - private MapValue map( Object... keyValues ) - { - return ValueUtils.asMapValue( MapUtil.map( keyValues ) ); - } - - private static String[] IRIS_CLASS_NAMES = new String[]{"Iris-setosa", "Iris-versicolor", "Iris-virginica"}; - - private static String IRIS_DATA = - "sepal_length,sepal_width,petal_length,petal_width,class_name\n" + "5.1,3.5,1.4,0.2,Iris-setosa\n" + "4.9,3.0,1.4,0.2,Iris-setosa\n" + - "4.7,3.2,1.3,0.2,Iris-setosa\n" + "4.6,3.1,1.5,0.2,Iris-setosa\n" + "5.0,3.6,1.4,0.2,Iris-setosa\n" + "5.4,3.9,1.7,0.4,Iris-setosa\n" + - "4.6,3.4,1.4,0.3,Iris-setosa\n" + "5.0,3.4,1.5,0.2,Iris-setosa\n" + "4.4,2.9,1.4,0.2,Iris-setosa\n" + "4.9,3.1,1.5,0.1,Iris-setosa\n" + - "5.4,3.7,1.5,0.2,Iris-setosa\n" + "4.8,3.4,1.6,0.2,Iris-setosa\n" + "4.8,3.0,1.4,0.1,Iris-setosa\n" + "4.3,3.0,1.1,0.1,Iris-setosa\n" + - "5.8,4.0,1.2,0.2,Iris-setosa\n" + "5.7,4.4,1.5,0.4,Iris-setosa\n" + "5.4,3.9,1.3,0.4,Iris-setosa\n" + "5.1,3.5,1.4,0.3,Iris-setosa\n" + - "5.7,3.8,1.7,0.3,Iris-setosa\n" + "5.1,3.8,1.5,0.3,Iris-setosa\n" + "5.4,3.4,1.7,0.2,Iris-setosa\n" + "5.1,3.7,1.5,0.4,Iris-setosa\n" + - "4.6,3.6,1.0,0.2,Iris-setosa\n" + "5.1,3.3,1.7,0.5,Iris-setosa\n" + "4.8,3.4,1.9,0.2,Iris-setosa\n" + "5.0,3.0,1.6,0.2,Iris-setosa\n" + - "5.0,3.4,1.6,0.4,Iris-setosa\n" + "5.2,3.5,1.5,0.2,Iris-setosa\n" + "5.2,3.4,1.4,0.2,Iris-setosa\n" + "4.7,3.2,1.6,0.2,Iris-setosa\n" + - "4.8,3.1,1.6,0.2,Iris-setosa\n" + "5.4,3.4,1.5,0.4,Iris-setosa\n" + "5.2,4.1,1.5,0.1,Iris-setosa\n" + "5.5,4.2,1.4,0.2,Iris-setosa\n" + - "4.9,3.1,1.5,0.2,Iris-setosa\n" + "5.0,3.2,1.2,0.2,Iris-setosa\n" + "5.5,3.5,1.3,0.2,Iris-setosa\n" + "4.9,3.6,1.4,0.1,Iris-setosa\n" + - "4.4,3.0,1.3,0.2,Iris-setosa\n" + "5.1,3.4,1.5,0.2,Iris-setosa\n" + "5.0,3.5,1.3,0.3,Iris-setosa\n" + "4.5,2.3,1.3,0.3,Iris-setosa\n" + - "4.4,3.2,1.3,0.2,Iris-setosa\n" + "5.0,3.5,1.6,0.6,Iris-setosa\n" + "5.1,3.8,1.9,0.4,Iris-setosa\n" + "4.8,3.0,1.4,0.3,Iris-setosa\n" + - "5.1,3.8,1.6,0.2,Iris-setosa\n" + "4.6,3.2,1.4,0.2,Iris-setosa\n" + "5.3,3.7,1.5,0.2,Iris-setosa\n" + "5.0,3.3,1.4,0.2,Iris-setosa\n" + - "7.0,3.2,4.7,1.4,Iris-versicolor\n" + "6.4,3.2,4.5,1.5,Iris-versicolor\n" + "6.9,3.1,4.9,1.5,Iris-versicolor\n" + - "5.5,2.3,4.0,1.3,Iris-versicolor\n" + "6.5,2.8,4.6,1.5,Iris-versicolor\n" + "5.7,2.8,4.5,1.3,Iris-versicolor\n" + - "6.3,3.3,4.7,1.6,Iris-versicolor\n" + "4.9,2.4,3.3,1.0,Iris-versicolor\n" + "6.6,2.9,4.6,1.3,Iris-versicolor\n" + - "5.2,2.7,3.9,1.4,Iris-versicolor\n" + "5.0,2.0,3.5,1.0,Iris-versicolor\n" + "5.9,3.0,4.2,1.5,Iris-versicolor\n" + - "6.0,2.2,4.0,1.0,Iris-versicolor\n" + "6.1,2.9,4.7,1.4,Iris-versicolor\n" + "5.6,2.9,3.6,1.3,Iris-versicolor\n" + - "6.7,3.1,4.4,1.4,Iris-versicolor\n" + "5.6,3.0,4.5,1.5,Iris-versicolor\n" + "5.8,2.7,4.1,1.0,Iris-versicolor\n" + - "6.2,2.2,4.5,1.5,Iris-versicolor\n" + "5.6,2.5,3.9,1.1,Iris-versicolor\n" + "5.9,3.2,4.8,1.8,Iris-versicolor\n" + - "6.1,2.8,4.0,1.3,Iris-versicolor\n" + "6.3,2.5,4.9,1.5,Iris-versicolor\n" + "6.1,2.8,4.7,1.2,Iris-versicolor\n" + - "6.4,2.9,4.3,1.3,Iris-versicolor\n" + "6.6,3.0,4.4,1.4,Iris-versicolor\n" + "6.8,2.8,4.8,1.4,Iris-versicolor\n" + - "6.7,3.0,5.0,1.7,Iris-versicolor\n" + "6.0,2.9,4.5,1.5,Iris-versicolor\n" + "5.7,2.6,3.5,1.0,Iris-versicolor\n" + - "5.5,2.4,3.8,1.1,Iris-versicolor\n" + "5.5,2.4,3.7,1.0,Iris-versicolor\n" + "5.8,2.7,3.9,1.2,Iris-versicolor\n" + - "6.0,2.7,5.1,1.6,Iris-versicolor\n" + "5.4,3.0,4.5,1.5,Iris-versicolor\n" + "6.0,3.4,4.5,1.6,Iris-versicolor\n" + - "6.7,3.1,4.7,1.5,Iris-versicolor\n" + "6.3,2.3,4.4,1.3,Iris-versicolor\n" + "5.6,3.0,4.1,1.3,Iris-versicolor\n" + - "5.5,2.5,4.0,1.3,Iris-versicolor\n" + "5.5,2.6,4.4,1.2,Iris-versicolor\n" + "6.1,3.0,4.6,1.4,Iris-versicolor\n" + - "5.8,2.6,4.0,1.2,Iris-versicolor\n" + "5.0,2.3,3.3,1.0,Iris-versicolor\n" + "5.6,2.7,4.2,1.3,Iris-versicolor\n" + - "5.7,3.0,4.2,1.2,Iris-versicolor\n" + "5.7,2.9,4.2,1.3,Iris-versicolor\n" + "6.2,2.9,4.3,1.3,Iris-versicolor\n" + - "5.1,2.5,3.0,1.1,Iris-versicolor\n" + "5.7,2.8,4.1,1.3,Iris-versicolor\n" + "6.3,3.3,6.0,2.5,Iris-virginica\n" + - "5.8,2.7,5.1,1.9,Iris-virginica\n" + "7.1,3.0,5.9,2.1,Iris-virginica\n" + "6.3,2.9,5.6,1.8,Iris-virginica\n" + - "6.5,3.0,5.8,2.2,Iris-virginica\n" + "7.6,3.0,6.6,2.1,Iris-virginica\n" + "4.9,2.5,4.5,1.7,Iris-virginica\n" + - "7.3,2.9,6.3,1.8,Iris-virginica\n" + "6.7,2.5,5.8,1.8,Iris-virginica\n" + "7.2,3.6,6.1,2.5,Iris-virginica\n" + - "6.5,3.2,5.1,2.0,Iris-virginica\n" + "6.4,2.7,5.3,1.9,Iris-virginica\n" + "6.8,3.0,5.5,2.1,Iris-virginica\n" + - "5.7,2.5,5.0,2.0,Iris-virginica\n" + "5.8,2.8,5.1,2.4,Iris-virginica\n" + "6.4,3.2,5.3,2.3,Iris-virginica\n" + - "6.5,3.0,5.5,1.8,Iris-virginica\n" + "7.7,3.8,6.7,2.2,Iris-virginica\n" + "7.7,2.6,6.9,2.3,Iris-virginica\n" + - "6.0,2.2,5.0,1.5,Iris-virginica\n" + "6.9,3.2,5.7,2.3,Iris-virginica\n" + "5.6,2.8,4.9,2.0,Iris-virginica\n" + - "7.7,2.8,6.7,2.0,Iris-virginica\n" + "6.3,2.7,4.9,1.8,Iris-virginica\n" + "6.7,3.3,5.7,2.1,Iris-virginica\n" + - "7.2,3.2,6.0,1.8,Iris-virginica\n" + "6.2,2.8,4.8,1.8,Iris-virginica\n" + "6.1,3.0,4.9,1.8,Iris-virginica\n" + - "6.4,2.8,5.6,2.1,Iris-virginica\n" + "7.2,3.0,5.8,1.6,Iris-virginica\n" + "7.4,2.8,6.1,1.9,Iris-virginica\n" + - "7.9,3.8,6.4,2.0,Iris-virginica\n" + "6.4,2.8,5.6,2.2,Iris-virginica\n" + "6.3,2.8,5.1,1.5,Iris-virginica\n" + - "6.1,2.6,5.6,1.4,Iris-virginica\n" + "7.7,3.0,6.1,2.3,Iris-virginica\n" + "6.3,3.4,5.6,2.4,Iris-virginica\n" + - "6.4,3.1,5.5,1.8,Iris-virginica\n" + "6.0,3.0,4.8,1.8,Iris-virginica\n" + "6.9,3.1,5.4,2.1,Iris-virginica\n" + - "6.7,3.1,5.6,2.4,Iris-virginica\n" + "6.9,3.1,5.1,2.3,Iris-virginica\n" + "5.8,2.7,5.1,1.9,Iris-virginica\n" + - "6.8,3.2,5.9,2.3,Iris-virginica\n" + "6.7,3.3,5.7,2.5,Iris-virginica\n" + "6.7,3.0,5.2,2.3,Iris-virginica\n" + - "6.3,2.5,5.0,1.9,Iris-virginica\n" + "6.5,3.0,5.2,2.0,Iris-virginica\n" + "6.2,3.4,5.4,2.3,Iris-virginica\n" + - "5.9,3.0,5.1,1.8,Iris-virginica\n" + "\n"; - - private BoltStateMachine newStateMachine() - { - return env.newMachine( BoltProtocolV3.VERSION, boltChannel ); - } - - private HelloMessage newHelloMessage() - { - return new HelloMessage( MapUtil.map( "user_agent", USER_AGENT ) ); - } -} diff --git a/community/community-it/bolt-it/src/test/java/org/neo4j/bolt/v3/runtime/integration/BoltStateMachineStateTestBase.java b/community/community-it/bolt-it/src/test/java/org/neo4j/bolt/v3/runtime/integration/BoltStateMachineStateTestBase.java new file mode 100644 index 0000000000000..435fb1454f6b9 --- /dev/null +++ b/community/community-it/bolt-it/src/test/java/org/neo4j/bolt/v3/runtime/integration/BoltStateMachineStateTestBase.java @@ -0,0 +1,60 @@ +/* + * Copyright (c) 2002-2018 "Neo4j," + * Neo4j Sweden AB [http://neo4j.com] + * + * This file is part of Neo4j. + * + * Neo4j is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ +package org.neo4j.bolt.v3.runtime.integration; + +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.extension.RegisterExtension; + +import org.neo4j.bolt.BoltChannel; +import org.neo4j.bolt.v3.BoltProtocolV3; +import org.neo4j.bolt.v3.BoltStateMachineV3; +import org.neo4j.bolt.v3.messaging.request.HelloMessage; +import org.neo4j.helpers.collection.MapUtil; +import org.neo4j.values.virtual.MapValue; +import org.neo4j.values.virtual.VirtualValues; + +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.when; + +class BoltStateMachineStateTestBase +{ + protected static final MapValue EMPTY_PARAMS = VirtualValues.EMPTY_MAP; + protected static final String USER_AGENT = "BoltConnectionIT/0.0"; + protected static final BoltChannel boltChannel = mock( BoltChannel.class ); + + @BeforeEach + void setup() + { + when( boltChannel.id() ).thenReturn( "conn-v3-test-boltchannel-id" ); + } + + @RegisterExtension + static final SessionExtension env = new SessionExtension(); + + protected BoltStateMachineV3 newStateMachine() + { + return (BoltStateMachineV3) env.newMachine( BoltProtocolV3.VERSION, boltChannel ); + } + + protected static HelloMessage newHelloMessage() + { + return new HelloMessage( MapUtil.map( "user_agent", USER_AGENT ) ); + } +} diff --git a/community/community-it/bolt-it/src/test/java/org/neo4j/bolt/v3/runtime/integration/BoltV3TransportIT.java b/community/community-it/bolt-it/src/test/java/org/neo4j/bolt/v3/runtime/integration/BoltV3TransportIT.java new file mode 100644 index 0000000000000..63ec67c5dcc2a --- /dev/null +++ b/community/community-it/bolt-it/src/test/java/org/neo4j/bolt/v3/runtime/integration/BoltV3TransportIT.java @@ -0,0 +1,425 @@ +/* + * Copyright (c) 2002-2018 "Neo4j," + * Neo4j Sweden AB [http://neo4j.com] + * + * This file is part of Neo4j. + * + * Neo4j is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ +package org.neo4j.bolt.v3.runtime.integration; + +import org.hamcrest.Matcher; +import org.hamcrest.Matchers; +import org.junit.After; +import org.junit.Before; +import org.junit.Rule; +import org.junit.Test; +import org.junit.runner.RunWith; +import org.junit.runners.Parameterized; +import org.junit.runners.Parameterized.Parameter; + +import java.util.HashMap; +import java.util.List; +import java.util.Map; + +import org.neo4j.bolt.v1.messaging.request.DiscardAllMessage; +import org.neo4j.bolt.v1.messaging.request.PullAllMessage; +import org.neo4j.bolt.v1.messaging.request.ResetMessage; +import org.neo4j.bolt.v1.transport.integration.Neo4jWithSocket; +import org.neo4j.bolt.v1.transport.integration.TransportTestUtil; +import org.neo4j.bolt.v1.transport.socket.client.SocketConnection; +import org.neo4j.bolt.v1.transport.socket.client.TransportConnection; +import org.neo4j.bolt.v3.messaging.request.BeginMessage; +import org.neo4j.bolt.v3.messaging.request.HelloMessage; +import org.neo4j.bolt.v3.messaging.request.RunMessage; +import org.neo4j.helpers.HostnamePort; +import org.neo4j.helpers.collection.MapUtil; +import org.neo4j.kernel.api.exceptions.Status; +import org.neo4j.kernel.impl.util.ValueUtils; + +import static java.util.Arrays.asList; +import static java.util.Collections.singletonList; +import static org.hamcrest.CoreMatchers.allOf; +import static org.hamcrest.CoreMatchers.equalTo; +import static org.hamcrest.CoreMatchers.is; +import static org.hamcrest.MatcherAssert.assertThat; +import static org.hamcrest.Matchers.hasEntry; +import static org.hamcrest.Matchers.hasKey; +import static org.hamcrest.Matchers.not; +import static org.junit.runners.Parameterized.Parameters; +import static org.neo4j.bolt.v1.messaging.util.MessageMatchers.msgFailure; +import static org.neo4j.bolt.v1.messaging.util.MessageMatchers.msgIgnored; +import static org.neo4j.bolt.v1.messaging.util.MessageMatchers.msgRecord; +import static org.neo4j.bolt.v1.messaging.util.MessageMatchers.msgSuccess; +import static org.neo4j.bolt.v1.runtime.spi.StreamMatchers.eqRecord; +import static org.neo4j.bolt.v1.transport.integration.TransportTestUtil.eventuallyReceives; +import static org.neo4j.bolt.v3.messaging.BoltProtocolV3ComponentFactory.newMessageEncoder; +import static org.neo4j.bolt.v3.messaging.BoltProtocolV3ComponentFactory.newNeo4jPack; +import static org.neo4j.bolt.v3.messaging.request.CommitMessage.COMMIT_MESSAGE; +import static org.neo4j.bolt.v3.messaging.request.RollbackMessage.ROLLBACK_MESSAGE; +import static org.neo4j.graphdb.factory.GraphDatabaseSettings.auth_enabled; +import static org.neo4j.values.storable.Values.longValue; +import static org.neo4j.values.storable.Values.stringValue; + +@RunWith( Parameterized.class ) +public class BoltV3TransportIT +{ + private static final String USER_AGENT = "TestClient/3.0"; + + @Rule + public Neo4jWithSocket server = new Neo4jWithSocket( getClass(), settings -> settings.put( auth_enabled.name(), "false" ) ); + + @Parameter + public Class connectionClass; + + private HostnamePort address; + private TransportConnection connection; + private TransportTestUtil util; + + @Parameters( name = "{0}" ) + public static List> transports() + { + return asList( SocketConnection.class ); + //, WebSocketConnection.class, SecureSocketConnection.class, SecureWebSocketConnection.class ); + } + + @Before + public void setUp() throws Exception + { + address = server.lookupDefaultConnector(); + connection = connectionClass.newInstance(); + util = new TransportTestUtil( newNeo4jPack(), newMessageEncoder() ); + } + + @After + public void tearDown() throws Exception + { + if ( connection != null ) + { + connection.disconnect(); + } + } + + @Test + public void shouldNegotiateProtocolV3() throws Exception + { + connection.connect( address ).send( util.acceptedVersions( 3, 0, 0, 0 ) ).send( + util.chunk( new HelloMessage( MapUtil.map( "user_agent", USER_AGENT ) ) ) ); + + assertThat( connection, eventuallyReceives( new byte[]{0, 0, 0, 3} ) ); + Matcher> entryRoutingTableMatcher = hasEntry( is( "routing_table" ), equalTo( "dbms.cluster.routing.getRoutingTable" ) ); + assertThat( connection, util.eventuallyReceives( msgSuccess( allOf( hasKey( "server" ), hasKey( "connection_id" ), entryRoutingTableMatcher ) ) ) ); + } + + @Test + public void shouldNegotiateProtocolV2WhenClientSupportsBothV1V2AndV3() throws Exception + { + connection.connect( address ) + .send( util.acceptedVersions( 3, 2, 1, 0 ) ) + .send( util.chunk( new HelloMessage( MapUtil.map( "user_agent", USER_AGENT ) ) ) ); + + assertThat( connection, eventuallyReceives( new byte[]{0, 0, 0, 3} ) ); + } + + @Test + public void shouldRunSimpleStatement() throws Throwable + { + // When + negotiateBoltV3(); + connection.send( util.chunk( + new RunMessage( "UNWIND [1,2,3] AS a RETURN a, a * a AS a_squared" ), + PullAllMessage.INSTANCE ) ); + + // Then + Matcher> entryFieldMatcher = hasEntry( is( "fields" ), equalTo( asList( "a", "a_squared" ) ) ); + Matcher> entryTypeMatcher = hasEntry( is( "type" ), equalTo( "r" ) ); + assertThat( connection, util.eventuallyReceives( + msgSuccess( allOf( entryFieldMatcher, hasKey( "tx_id" ), hasKey( "result_available_after" ) ) ), + msgRecord( eqRecord( equalTo( longValue( 1L ) ), equalTo( longValue( 1L ) ) ) ), + msgRecord( eqRecord( equalTo( longValue( 2L ) ), equalTo( longValue( 4L ) ) ) ), + msgRecord( eqRecord( equalTo( longValue( 3L ) ), equalTo( longValue( 9L ) ) ) ), + msgSuccess( allOf( entryTypeMatcher, hasKey( "result_consumed_after" ) ) ) ) ); + } + + @Test + public void shouldRunSimpleStatementInTx() throws Throwable + { + // When + negotiateBoltV3(); + connection.send( util.chunk( + new BeginMessage(), + new RunMessage( "UNWIND [1,2,3] AS a RETURN a, a * a AS a_squared" ), + PullAllMessage.INSTANCE, + COMMIT_MESSAGE ) ); + + // Then + Matcher> entryFieldMatcher = hasEntry( is( "fields" ), equalTo( asList( "a", "a_squared" ) ) ); + Matcher> entryTypeMatcher = hasEntry( is( "type" ), equalTo( "r" ) ); + assertThat( connection, util.eventuallyReceives( + msgSuccess( allOf( hasKey( "tx_id" ) ) ), + msgSuccess( allOf( entryFieldMatcher, not( hasKey( "tx_id" ) ), hasKey( "result_available_after" ) ) ), + msgRecord( eqRecord( equalTo( longValue( 1L ) ), equalTo( longValue( 1L ) ) ) ), + msgRecord( eqRecord( equalTo( longValue( 2L ) ), equalTo( longValue( 4L ) ) ) ), + msgRecord( eqRecord( equalTo( longValue( 3L ) ), equalTo( longValue( 9L ) ) ) ), + msgSuccess( allOf( entryTypeMatcher, hasKey( "result_consumed_after" ) ) ), + msgSuccess( allOf( hasKey( "bookmark" ) ) ) ) ); + } + + @Test + public void shouldAllowRollbackSimpleStatementInTx() throws Throwable + { + // When + negotiateBoltV3(); + connection.send( util.chunk( + new BeginMessage(), + new RunMessage( "UNWIND [1,2,3] AS a RETURN a, a * a AS a_squared" ), + PullAllMessage.INSTANCE, + ROLLBACK_MESSAGE ) ); + + // Then + Matcher> entryFieldMatcher = hasEntry( is( "fields" ), equalTo( asList( "a", "a_squared" ) ) ); + Matcher> entryTypeMatcher = hasEntry( is( "type" ), equalTo( "r" ) ); + assertThat( connection, util.eventuallyReceives( + msgSuccess( allOf( hasKey( "tx_id" ) ) ), + msgSuccess( allOf( entryFieldMatcher, not( hasKey( "tx_id" ) ), hasKey( "result_available_after" ) ) ), + msgRecord( eqRecord( equalTo( longValue( 1L ) ), equalTo( longValue( 1L ) ) ) ), + msgRecord( eqRecord( equalTo( longValue( 2L ) ), equalTo( longValue( 4L ) ) ) ), + msgRecord( eqRecord( equalTo( longValue( 3L ) ), equalTo( longValue( 9L ) ) ) ), + msgSuccess( allOf( entryTypeMatcher, hasKey( "result_consumed_after" ) ) ), + msgSuccess() ) ); + } + + @Test + public void shouldRespondWithMetadataToDiscardAll() throws Throwable + { + // When + negotiateBoltV3(); + connection.send( util.chunk( + new RunMessage( "UNWIND [1,2,3] AS a RETURN a, a * a AS a_squared" ), + DiscardAllMessage.INSTANCE ) ); + + // Then + Matcher> entryTypeMatcher = hasEntry( is( "type" ), equalTo( "r" ) ); + Matcher> entryFieldsMatcher = hasEntry( is( "fields" ), equalTo( asList( "a", "a_squared" ) ) ); + assertThat( connection, util.eventuallyReceives( + msgSuccess( allOf( entryFieldsMatcher, hasKey( "tx_id" ), hasKey( "result_available_after" ) ) ), + msgSuccess( allOf( entryTypeMatcher, hasKey( "result_consumed_after" ) ) ) ) ); + } + + @Test + public void shouldBeAbleToRunQueryAfterReset() throws Throwable + { + // Given + negotiateBoltV3(); + connection.send( util.chunk( + new RunMessage( "QINVALID" ), + PullAllMessage.INSTANCE ) ); + + assertThat( connection, util.eventuallyReceives( + msgFailure( Status.Statement.SyntaxError, + String.format( "Invalid input 'Q': expected (line 1, column 1 (offset: 0))%n" + + "\"QINVALID\"%n" + + " ^" ) ), + msgIgnored() ) ); + + // When + connection.send( util.chunk( ResetMessage.INSTANCE, new RunMessage( "RETURN 1" ), PullAllMessage.INSTANCE ) ); + + // Then + assertThat( connection, util.eventuallyReceives( + msgSuccess(), + msgSuccess(), + msgRecord( eqRecord( equalTo( longValue( 1L ) ) ) ), + msgSuccess() ) ); + } + + @Test + public void shouldRunProcedure() throws Throwable + { + // Given + negotiateBoltV3(); + connection.send( util.chunk( + new RunMessage( "CREATE (n:Test {age: 2}) RETURN n.age AS age" ), + PullAllMessage.INSTANCE ) ); + + Matcher> ageMatcher = hasEntry( is( "fields" ), equalTo( singletonList( "age" ) ) ); + assertThat( connection, util.eventuallyReceives( + msgSuccess( allOf( ageMatcher, hasKey( "tx_id" ), hasKey( "result_available_after" ) ) ), + msgRecord( eqRecord( equalTo( longValue( 2L ) ) ) ), + msgSuccess() ) ); + + // When + connection.send( util.chunk( + new RunMessage( "CALL db.labels() YIELD label" ), + PullAllMessage.INSTANCE ) ); + + // Then + Matcher> entryFieldsMatcher = hasEntry( is( "fields" ), equalTo( singletonList( "label" ) ) ); + assertThat( connection, util.eventuallyReceives( + msgSuccess( allOf( entryFieldsMatcher, hasKey( "tx_id" ), hasKey( "result_available_after" ) ) ), + msgRecord( eqRecord( Matchers.equalTo( stringValue( "Test" ) ) ) ), + msgSuccess() + ) ); + } + + @Test + public void shouldHandleDeletedNodes() throws Throwable + { + // When + negotiateBoltV3(); + connection.send( util.chunk( + new RunMessage( "CREATE (n:Test) DELETE n RETURN n" ), + PullAllMessage.INSTANCE ) ); + + // Then + Matcher> entryFieldsMatcher = hasEntry( is( "fields" ), equalTo( singletonList( "n" ) ) ); + assertThat( connection, util.eventuallyReceives( + msgSuccess( allOf( entryFieldsMatcher, hasKey("tx_id"), hasKey( "result_available_after" ) ) ) ) ); + + // + //Record(0x71) { + // fields: [ Node(0x4E) { + // id: 00 + // labels: [] (90) + // props: {} (A)] + //} + assertThat( connection, + eventuallyReceives( bytes( 0x00, 0x08, 0xB1, 0x71, 0x91, + 0xB3, 0x4E, 0x00, 0x90, 0xA0, 0x00, 0x00 ) ) ); + assertThat( connection, util.eventuallyReceives( msgSuccess() ) ); + } + + @Test + public void shouldHandleDeletedRelationships() throws Throwable + { + // When + negotiateBoltV3(); + connection.send( util.chunk( + new RunMessage( "CREATE ()-[r:T {prop: 42}]->() DELETE r RETURN r" ), + PullAllMessage.INSTANCE ) ); + + // Then + Matcher> entryFieldsMatcher = hasEntry( is( "fields" ), equalTo( singletonList( "r" ) ) ); + assertThat( connection, util.eventuallyReceives( + msgSuccess( allOf( entryFieldsMatcher, hasKey( "tx_id" ), hasKey( "result_available_after" ) ) ) ) ); + + // + //Record(0x71) { + // fields: [ Relationship(0x52) { + // relId: 00 + // startId: 00 + // endId: 01 + // type: "T" (81 54) + // props: {} (A0)] + //} + assertThat( connection, + eventuallyReceives( bytes( 0x00, 0x0B, 0xB1, 0x71, 0x91, + 0xB5, 0x52, 0x00, 0x00, 0x01, 0x81, 0x54, 0xA0, 0x00, 0x00 ) ) ); + assertThat( connection, util.eventuallyReceives( msgSuccess() ) ); + } + + @Test + public void shouldNotLeakStatsToNextStatement() throws Throwable + { + // Given + negotiateBoltV3(); + connection.send( util.chunk( + new RunMessage( "CREATE (n)" ), + PullAllMessage.INSTANCE ) ); + assertThat( connection, util.eventuallyReceives( + msgSuccess(), + msgSuccess() ) ); + + // When + connection.send( + util.chunk( + new RunMessage( "RETURN 1" ), + PullAllMessage.INSTANCE ) ); + + // Then + Matcher> typeMatcher = hasEntry( is( "type" ), equalTo( "r" ) ); + assertThat( connection, util.eventuallyReceives( + msgSuccess(), + msgRecord( eqRecord( equalTo( longValue( 1L ) ) ) ), + msgSuccess( allOf( typeMatcher, hasKey( "result_consumed_after" ) ) ) ) ); + } + + private byte[] bytes( int... ints ) + { + byte[] bytes = new byte[ints.length]; + for ( int i = 0; i < ints.length; i++ ) + { + bytes[i] = (byte) ints[i]; + } + return bytes; + } + + @Test + public void shouldFailNicelyOnNullKeysInMap() throws Throwable + { + //Given + HashMap params = new HashMap<>(); + HashMap inner = new HashMap<>(); + inner.put( null, 42L ); + inner.put( "foo", 1337L ); + params.put( "p", inner ); + + // When + negotiateBoltV3(); + connection.send( util.chunk( + new RunMessage( "RETURN {p}", ValueUtils.asMapValue( params ) ), + PullAllMessage.INSTANCE ) ); + + // Then + assertThat( connection, util.eventuallyReceives( + msgFailure( Status.Request.Invalid, + "Value `null` is not supported as key in maps, must be a non-nullable string." ), + msgIgnored() ) ); + + connection.send( util.chunk( ResetMessage.INSTANCE, new RunMessage( "RETURN 1" ), PullAllMessage.INSTANCE ) ); + + // Then + assertThat( connection, util.eventuallyReceives( + msgSuccess(), + msgSuccess(), + msgRecord( eqRecord( equalTo( longValue( 1L ) ) ) ), + msgSuccess() ) ); + } + + @Test + public void shouldFailNicelyWhenDroppingUnknownIndex() throws Throwable + { + // When + negotiateBoltV3(); + connection.send( util.chunk( + new RunMessage( "DROP INDEX on :Movie12345(id)" ), + PullAllMessage.INSTANCE ) ); + + // Then + assertThat( connection, util.eventuallyReceives( + msgFailure( Status.Schema.IndexDropFailed, + "Unable to drop index on :Movie12345(id): No such INDEX ON :Movie12345(id)." ), + msgIgnored() ) ); + } + + private void negotiateBoltV3() throws Exception + { + connection.connect( address ) + .send( util.acceptedVersions( 3, 0, 0, 0 ) ) + .send( util.chunk( new HelloMessage( MapUtil.map( "user_agent", USER_AGENT ) ) ) ); + + assertThat( connection, eventuallyReceives( new byte[]{0, 0, 0, 3} ) ); + assertThat( connection, util.eventuallyReceives( msgSuccess() ) ); + } +} diff --git a/community/community-it/bolt-it/src/test/java/org/neo4j/bolt/v3/runtime/integration/ConnectedStateIT.java b/community/community-it/bolt-it/src/test/java/org/neo4j/bolt/v3/runtime/integration/ConnectedStateIT.java new file mode 100644 index 0000000000000..abdf5cd81cc85 --- /dev/null +++ b/community/community-it/bolt-it/src/test/java/org/neo4j/bolt/v3/runtime/integration/ConnectedStateIT.java @@ -0,0 +1,112 @@ +/* + * Copyright (c) 2002-2018 "Neo4j," + * Neo4j Sweden AB [http://neo4j.com] + * + * This file is part of Neo4j. + * + * Neo4j is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ +package org.neo4j.bolt.v3.runtime.integration; + +import org.junit.jupiter.api.Test; +import org.junit.jupiter.params.ParameterizedTest; +import org.junit.jupiter.params.provider.MethodSource; + +import java.util.stream.Stream; + +import org.neo4j.bolt.messaging.BoltIOException; +import org.neo4j.bolt.messaging.RequestMessage; +import org.neo4j.bolt.testing.BoltResponseRecorder; +import org.neo4j.bolt.testing.RecordedBoltResponse; +import org.neo4j.bolt.v1.messaging.request.DiscardAllMessage; +import org.neo4j.bolt.v1.messaging.request.InitMessage; +import org.neo4j.bolt.v1.messaging.request.PullAllMessage; +import org.neo4j.bolt.v1.messaging.request.ResetMessage; +import org.neo4j.bolt.v1.runtime.ConnectedState; +import org.neo4j.bolt.v3.BoltStateMachineV3; +import org.neo4j.bolt.v3.messaging.request.BeginMessage; +import org.neo4j.bolt.v3.messaging.request.RunMessage; +import org.neo4j.bolt.v3.runtime.ReadyState; +import org.neo4j.kernel.api.exceptions.Status; +import org.neo4j.kernel.internal.Version; + +import static java.util.Collections.emptyMap; +import static org.hamcrest.CoreMatchers.instanceOf; +import static org.hamcrest.MatcherAssert.assertThat; +import static org.neo4j.bolt.testing.BoltMatchers.failedWithStatus; +import static org.neo4j.bolt.testing.BoltMatchers.succeededWithMetadata; +import static org.neo4j.bolt.testing.BoltMatchers.verifyKillsConnection; +import static org.neo4j.bolt.v1.runtime.BoltStateMachineV1SPI.BOLT_SERVER_VERSION_PREFIX; +import static org.neo4j.bolt.v3.messaging.request.CommitMessage.COMMIT_MESSAGE; +import static org.neo4j.bolt.v3.messaging.request.RollbackMessage.ROLLBACK_MESSAGE; + +class ConnectedStateIT extends BoltStateMachineStateTestBase +{ + @Test + void shouldExecuteStatement() throws Throwable + { + // Given + BoltStateMachineV3 machine = newStateMachine(); + BoltResponseRecorder recorder = new BoltResponseRecorder(); + + // When + machine.process( newHelloMessage(), recorder ); + + // Then + RecordedBoltResponse response = recorder.nextResponse(); + assertThat( response, succeededWithMetadata( "server", BOLT_SERVER_VERSION_PREFIX + Version.getNeo4jVersion() ) ); + assertThat( response, succeededWithMetadata( "routing_table", "dbms.cluster.routing.getRoutingTable" ) ); + assertThat( response, succeededWithMetadata( "connection_id", "conn-v3-test-boltchannel-id" ) ); + assertThat( machine.state(), instanceOf( ReadyState.class ) ); + } + + @ParameterizedTest + @MethodSource( "illegalV3Messages" ) + void shouldCloseConnectionOnIllegalV3Messages( RequestMessage message ) throws Throwable + { + shouldCloseConnectionOnIllegalMessages( message ); + } + + @ParameterizedTest + @MethodSource( "illegalV2Messages" ) + void shouldCloseConnectionOnIllegalV2Messages( RequestMessage message ) throws Throwable + { + shouldCloseConnectionOnIllegalMessages( message ); + } + + private void shouldCloseConnectionOnIllegalMessages( RequestMessage message ) throws InterruptedException + { + // Given + BoltStateMachineV3 machine = newStateMachine(); + + // when + BoltResponseRecorder recorder = new BoltResponseRecorder(); + verifyKillsConnection( () -> machine.process( message, recorder ) ); + + // then + assertThat( recorder.nextResponse(), failedWithStatus( Status.Request.Invalid ) ); + assertThat( machine.state(), instanceOf( ConnectedState.class ) ); + } + + private static Stream illegalV3Messages() throws BoltIOException + { + return Stream.of( new RunMessage( "RETURN 1", EMPTY_PARAMS, EMPTY_PARAMS ), DiscardAllMessage.INSTANCE, PullAllMessage.INSTANCE, new BeginMessage(), + COMMIT_MESSAGE, ROLLBACK_MESSAGE, ResetMessage.INSTANCE ); + } + + private static Stream illegalV2Messages() + { + return Stream.of( new org.neo4j.bolt.v1.messaging.request.RunMessage( "RETURN 1", EMPTY_PARAMS ), new InitMessage( USER_AGENT, emptyMap() ) ); + } +} diff --git a/community/community-it/bolt-it/src/test/java/org/neo4j/bolt/v3/runtime/integration/InterruptedStateIT.java b/community/community-it/bolt-it/src/test/java/org/neo4j/bolt/v3/runtime/integration/InterruptedStateIT.java new file mode 100644 index 0000000000000..a1243cf60749a --- /dev/null +++ b/community/community-it/bolt-it/src/test/java/org/neo4j/bolt/v3/runtime/integration/InterruptedStateIT.java @@ -0,0 +1,135 @@ +/* + * Copyright (c) 2002-2018 "Neo4j," + * Neo4j Sweden AB [http://neo4j.com] + * + * This file is part of Neo4j. + * + * Neo4j is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ +package org.neo4j.bolt.v3.runtime.integration; + +import org.junit.jupiter.api.Test; +import org.junit.jupiter.params.ParameterizedTest; +import org.junit.jupiter.params.provider.MethodSource; + +import java.util.stream.Stream; + +import org.neo4j.bolt.messaging.BoltIOException; +import org.neo4j.bolt.messaging.RequestMessage; +import org.neo4j.bolt.runtime.BoltConnectionFatality; +import org.neo4j.bolt.testing.BoltResponseRecorder; +import org.neo4j.bolt.v1.messaging.request.DiscardAllMessage; +import org.neo4j.bolt.v1.messaging.request.InterruptSignal; +import org.neo4j.bolt.v1.messaging.request.PullAllMessage; +import org.neo4j.bolt.v1.messaging.request.ResetMessage; +import org.neo4j.bolt.v1.messaging.request.RunMessage; +import org.neo4j.bolt.v1.runtime.InterruptedState; +import org.neo4j.bolt.v3.BoltStateMachineV3; +import org.neo4j.bolt.v3.messaging.request.BeginMessage; +import org.neo4j.bolt.v3.runtime.ReadyState; + +import static org.hamcrest.CoreMatchers.instanceOf; +import static org.hamcrest.MatcherAssert.assertThat; +import static org.neo4j.bolt.testing.BoltMatchers.succeeded; +import static org.neo4j.bolt.testing.BoltMatchers.wasIgnored; +import static org.neo4j.bolt.testing.NullResponseHandler.nullResponseHandler; +import static org.neo4j.bolt.v3.messaging.request.CommitMessage.COMMIT_MESSAGE; +import static org.neo4j.bolt.v3.messaging.request.RollbackMessage.ROLLBACK_MESSAGE; + +class InterruptedStateIT extends BoltStateMachineStateTestBase +{ + @Test + void shouldMoveReadyOnReset_succ() throws Throwable + { + // Given + BoltStateMachineV3 machine = getBoltStateMachineInInterruptedState(); + + // When + BoltResponseRecorder recorder = new BoltResponseRecorder(); + machine.process( ResetMessage.INSTANCE, recorder ); + + // Then + assertThat( recorder.nextResponse(), succeeded() ); + assertThat( machine.state(), instanceOf( ReadyState.class ) ); + } + + @Test + void shouldStayInInterruptedOnMoreReset() throws Throwable + { + // Given + BoltStateMachineV3 machine = getBoltStateMachineInInterruptedState(); + machine.interrupt(); + machine.interrupt(); // need two reset to recover + + // When & Then + machine.process( ResetMessage.INSTANCE, nullResponseHandler() ); + assertThat( machine.state(), instanceOf( InterruptedState.class ) ); + + BoltResponseRecorder recorder = new BoltResponseRecorder(); + machine.process( ResetMessage.INSTANCE, recorder ); + assertThat( recorder.nextResponse(), succeeded() ); + assertThat( machine.state(), instanceOf( ReadyState.class ) ); + } + + @Test + void shouldStayInInterruptedOnInterruptedSignal() throws Throwable + { + // Given + BoltStateMachineV3 machine = getBoltStateMachineInInterruptedState(); + + // When + BoltResponseRecorder recorder = new BoltResponseRecorder(); + machine.process( InterruptSignal.INSTANCE, recorder ); + + // Then + assertThat( recorder.nextResponse(), succeeded() ); + assertThat( machine.state(), instanceOf( InterruptedState.class ) ); + } + + @ParameterizedTest + @MethodSource( "illegalV3Messages" ) + void shouldCloseConnectionOnIllegalV3Messages( RequestMessage message ) throws Throwable + { + shouldCloseConnectionOnIllegalMessages( message ); + } + + private void shouldCloseConnectionOnIllegalMessages( RequestMessage message ) throws InterruptedException, BoltConnectionFatality + { + // Given + BoltStateMachineV3 machine = getBoltStateMachineInInterruptedState(); + + // when + BoltResponseRecorder recorder = new BoltResponseRecorder(); + machine.process( message, recorder ); + + // then + assertThat( recorder.nextResponse(), wasIgnored() ); + assertThat( machine.state(), instanceOf( InterruptedState.class ) ); + } + + private BoltStateMachineV3 getBoltStateMachineInInterruptedState() throws BoltConnectionFatality + { + BoltStateMachineV3 machine = newStateMachine(); + machine.process( newHelloMessage(), nullResponseHandler() ); + machine.process( InterruptSignal.INSTANCE, nullResponseHandler() ); + assertThat( machine.state(), instanceOf( InterruptedState.class ) ); + return machine; + } + + private static Stream illegalV3Messages() throws BoltIOException + { + return Stream.of( newHelloMessage(), DiscardAllMessage.INSTANCE, PullAllMessage.INSTANCE, new BeginMessage(), COMMIT_MESSAGE, ROLLBACK_MESSAGE, + new RunMessage( "A cypher query" ) ); + } +} diff --git a/community/community-it/bolt-it/src/test/java/org/neo4j/bolt/v3/runtime/integration/ReadyStateIT.java b/community/community-it/bolt-it/src/test/java/org/neo4j/bolt/v3/runtime/integration/ReadyStateIT.java new file mode 100644 index 0000000000000..787a121a10795 --- /dev/null +++ b/community/community-it/bolt-it/src/test/java/org/neo4j/bolt/v3/runtime/integration/ReadyStateIT.java @@ -0,0 +1,189 @@ +/* + * Copyright (c) 2002-2018 "Neo4j," + * Neo4j Sweden AB [http://neo4j.com] + * + * This file is part of Neo4j. + * + * Neo4j is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ +package org.neo4j.bolt.v3.runtime.integration; + +import org.junit.jupiter.api.Test; +import org.junit.jupiter.params.ParameterizedTest; +import org.junit.jupiter.params.provider.MethodSource; + +import java.util.stream.Stream; + +import org.neo4j.bolt.messaging.RequestMessage; +import org.neo4j.bolt.runtime.BoltConnectionFatality; +import org.neo4j.bolt.testing.BoltResponseRecorder; +import org.neo4j.bolt.testing.RecordedBoltResponse; +import org.neo4j.bolt.v1.messaging.request.DiscardAllMessage; +import org.neo4j.bolt.v1.messaging.request.InitMessage; +import org.neo4j.bolt.v1.messaging.request.InterruptSignal; +import org.neo4j.bolt.v1.messaging.request.PullAllMessage; +import org.neo4j.bolt.v1.runtime.InterruptedState; +import org.neo4j.bolt.v3.BoltStateMachineV3; +import org.neo4j.bolt.v3.messaging.request.BeginMessage; +import org.neo4j.bolt.v3.messaging.request.RunMessage; +import org.neo4j.bolt.v3.runtime.FailedState; +import org.neo4j.bolt.v3.runtime.ReadyState; +import org.neo4j.bolt.v3.runtime.StreamingState; +import org.neo4j.bolt.v3.runtime.TransactionReadyState; +import org.neo4j.kernel.api.exceptions.Status; + +import static java.util.Collections.emptyMap; +import static org.hamcrest.CoreMatchers.instanceOf; +import static org.hamcrest.MatcherAssert.assertThat; +import static org.junit.jupiter.api.Assertions.assertTrue; +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.when; +import static org.neo4j.bolt.testing.BoltMatchers.failedWithStatus; +import static org.neo4j.bolt.testing.BoltMatchers.succeeded; +import static org.neo4j.bolt.testing.BoltMatchers.verifyKillsConnection; +import static org.neo4j.bolt.testing.NullResponseHandler.nullResponseHandler; +import static org.neo4j.bolt.v3.messaging.request.CommitMessage.COMMIT_MESSAGE; +import static org.neo4j.bolt.v3.messaging.request.RollbackMessage.ROLLBACK_MESSAGE; + +class ReadyStateIT extends BoltStateMachineStateTestBase +{ + @Test + void shouldMoveToStreamingOnRun_succ() throws Throwable + { + // Given + BoltStateMachineV3 machine = newStateMachine(); + machine.process( newHelloMessage(), nullResponseHandler() ); + + // When + BoltResponseRecorder recorder = new BoltResponseRecorder(); + machine.process( new RunMessage( "CREATE (n {k:'k'}) RETURN n.k", EMPTY_PARAMS ), recorder ); + + // Then + + RecordedBoltResponse response = recorder.nextResponse(); + assertThat( response, succeeded() ); + assertTrue( response.hasMetadata( "fields" ) ); + assertTrue( response.hasMetadata( "result_available_after") ); + assertTrue( response.hasMetadata( "tx_id") ); + assertThat( machine.state(), instanceOf( StreamingState.class ) ); + } + + @Test + void shouldMoveToStreamingOnBegin_succ() throws Throwable + { + // Given + BoltStateMachineV3 machine = newStateMachine(); + machine.process( newHelloMessage(), nullResponseHandler() ); + + // When + BoltResponseRecorder recorder = new BoltResponseRecorder(); + machine.process( new BeginMessage(), recorder ); + + // Then + RecordedBoltResponse response = recorder.nextResponse(); + assertThat( response, succeeded() ); + assertTrue( response.hasMetadata( "tx_id") ); + assertThat( machine.state(), instanceOf( TransactionReadyState.class ) ); + } + + @Test + void shouldMoveToInterruptedOnInterrupt() throws Throwable + { + // Given + BoltStateMachineV3 machine = newStateMachine(); + machine.process( newHelloMessage(), nullResponseHandler() ); + + // When + BoltResponseRecorder recorder = new BoltResponseRecorder(); + machine.process( InterruptSignal.INSTANCE, recorder ); + + // Then + assertThat( machine.state(), instanceOf( InterruptedState.class ) ); + } + + @Test + void shouldMoveToFailedStateOnRun_fail() throws Throwable + { + // Given + BoltStateMachineV3 machine = newStateMachine(); + machine.process( newHelloMessage(), nullResponseHandler() ); + + // When + BoltResponseRecorder recorder = new BoltResponseRecorder(); + RunMessage runMessage = mock( RunMessage.class ); + when( runMessage.statement() ).thenThrow( new RuntimeException( "Fail" ) ); + machine.process( runMessage, recorder ); + + // Then + assertThat( recorder.nextResponse(), failedWithStatus( Status.General.UnknownError ) ); + assertThat( machine.state(), instanceOf( FailedState.class ) ); + } + + @Test + void shouldMoveToFailedStateOnBegin_fail() throws Throwable + { + // Given + BoltStateMachineV3 machine = newStateMachine(); + machine.process( newHelloMessage(), nullResponseHandler() ); + + // When + BoltResponseRecorder recorder = new BoltResponseRecorder(); + BeginMessage beginMessage = mock( BeginMessage.class ); + when( beginMessage.bookmark() ).thenThrow( new RuntimeException( "Fail" ) ); + machine.process( beginMessage, recorder ); + + // Then + assertThat( recorder.nextResponse(), failedWithStatus( Status.General.UnknownError ) ); + assertThat( machine.state(), instanceOf( FailedState.class ) ); + } + + @ParameterizedTest + @MethodSource( "illegalV3Messages" ) + void shouldCloseConnectionOnIllegalV3Messages( RequestMessage message ) throws Throwable + { + shouldCloseConnectionOnIllegalMessages( message ); + } + + @ParameterizedTest + @MethodSource( "illegalV2Messages" ) + void shouldCloseConnectionOnIllegalV2Messages( RequestMessage message ) throws Throwable + { + shouldCloseConnectionOnIllegalMessages( message ); + } + + private void shouldCloseConnectionOnIllegalMessages( RequestMessage message ) throws InterruptedException, BoltConnectionFatality + { + // Given + BoltStateMachineV3 machine = newStateMachine(); + machine.process( newHelloMessage(), nullResponseHandler() ); + + // when + BoltResponseRecorder recorder = new BoltResponseRecorder(); + verifyKillsConnection( () -> machine.process( message, recorder ) ); + + // then + assertThat( recorder.nextResponse(), failedWithStatus( Status.Request.Invalid ) ); + assertThat( machine.state(), instanceOf( ReadyState.class ) ); + } + + private static Stream illegalV3Messages() + { + return Stream.of( newHelloMessage(), DiscardAllMessage.INSTANCE, PullAllMessage.INSTANCE, COMMIT_MESSAGE, ROLLBACK_MESSAGE ); + } + + private static Stream illegalV2Messages() + { + return Stream.of( new InitMessage( USER_AGENT, emptyMap() ), new org.neo4j.bolt.v1.messaging.request.RunMessage( "RETURN 1", EMPTY_PARAMS ) ); + } +} diff --git a/community/community-it/bolt-it/src/test/java/org/neo4j/bolt/v3/runtime/integration/SessionExtension.java b/community/community-it/bolt-it/src/test/java/org/neo4j/bolt/v3/runtime/integration/SessionExtension.java new file mode 100644 index 0000000000000..23f28f1eb8179 --- /dev/null +++ b/community/community-it/bolt-it/src/test/java/org/neo4j/bolt/v3/runtime/integration/SessionExtension.java @@ -0,0 +1,148 @@ +/* + * Copyright (c) 2002-2018 "Neo4j," + * Neo4j Sweden AB [http://neo4j.com] + * + * This file is part of Neo4j. + * + * Neo4j is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ +package org.neo4j.bolt.v3.runtime.integration; + +import org.junit.jupiter.api.extension.AfterEachCallback; +import org.junit.jupiter.api.extension.BeforeEachCallback; +import org.junit.jupiter.api.extension.ExtensionContext; + +import java.io.File; +import java.io.IOException; +import java.io.PrintWriter; +import java.net.URL; +import java.time.Clock; +import java.util.HashMap; +import java.util.LinkedList; +import java.util.Map; + +import org.neo4j.bolt.BoltChannel; +import org.neo4j.bolt.runtime.BoltStateMachine; +import org.neo4j.bolt.runtime.BoltStateMachineFactoryImpl; +import org.neo4j.bolt.security.auth.Authentication; +import org.neo4j.bolt.security.auth.BasicAuthentication; +import org.neo4j.bolt.v1.BoltProtocolV1; +import org.neo4j.graphdb.DependencyResolver; +import org.neo4j.graphdb.config.Setting; +import org.neo4j.graphdb.factory.GraphDatabaseSettings; +import org.neo4j.kernel.AvailabilityGuard; +import org.neo4j.kernel.api.bolt.BoltConnectionTracker; +import org.neo4j.kernel.api.security.AuthManager; +import org.neo4j.kernel.api.security.UserManagerSupplier; +import org.neo4j.kernel.configuration.Config; +import org.neo4j.kernel.impl.logging.NullLogService; +import org.neo4j.kernel.impl.transaction.log.TransactionIdStore; +import org.neo4j.kernel.internal.GraphDatabaseAPI; +import org.neo4j.test.TestGraphDatabaseFactory; +import org.neo4j.udc.UsageData; + +public class SessionExtension implements BeforeEachCallback, AfterEachCallback +{ + private GraphDatabaseAPI gdb; + private BoltStateMachineFactoryImpl boltFactory; + private LinkedList runningMachines = new LinkedList<>(); + private boolean authEnabled; + + private Authentication authentication( AuthManager authManager, UserManagerSupplier userManagerSupplier ) + { + return new BasicAuthentication( authManager, userManagerSupplier ); + } + + BoltStateMachine newMachine( BoltChannel boltChannel ) + { + return newMachine( BoltProtocolV1.VERSION, boltChannel ); + } + + public BoltStateMachine newMachine( long version, BoltChannel boltChannel ) + { + if ( boltFactory == null ) + { + throw new IllegalStateException( "Cannot access test environment before test is running." ); + } + BoltStateMachine machine = boltFactory.newStateMachine( version, boltChannel ); + runningMachines.add( machine ); + return machine; + } + + SessionExtension withAuthEnabled( boolean authEnabled ) + { + this.authEnabled = authEnabled; + return this; + } + + public URL putTmpFile( String prefix, String suffix, String contents ) throws IOException + { + File tmpFile = File.createTempFile( prefix, suffix, null ); + tmpFile.deleteOnExit(); + try ( PrintWriter out = new PrintWriter( tmpFile ) ) + { + out.println( contents); + } + return tmpFile.toURI().toURL(); + } + + public GraphDatabaseAPI graph() + { + return gdb; + } + + public long lastClosedTxId() + { + return gdb.getDependencyResolver().resolveDependency( TransactionIdStore.class ).getLastClosedTransactionId(); + } + + @Override + public void beforeEach( ExtensionContext extensionContext ) throws Exception + { + Map,String> config = new HashMap<>(); + config.put( GraphDatabaseSettings.auth_enabled, Boolean.toString( authEnabled ) ); + gdb = (GraphDatabaseAPI) new TestGraphDatabaseFactory().newImpermanentDatabase( config ); + DependencyResolver resolver = gdb.getDependencyResolver(); + Authentication authentication = authentication( resolver.resolveDependency( AuthManager.class ), + resolver.resolveDependency( UserManagerSupplier.class ) ); + boltFactory = new BoltStateMachineFactoryImpl( + gdb, + new UsageData( null ), + resolver.resolveDependency( AvailabilityGuard.class ), + authentication, + BoltConnectionTracker.NOOP, + Clock.systemUTC(), + Config.defaults(), + NullLogService.getInstance() + ); + } + + @Override + public void afterEach( ExtensionContext extensionContext ) throws Exception + { + try + { + if ( runningMachines != null ) + { + runningMachines.forEach( BoltStateMachine::close ); + } + } + catch ( Throwable e ) + { + e.printStackTrace(); + } + + gdb.shutdown(); + } +} diff --git a/community/community-it/bolt-it/src/test/java/org/neo4j/bolt/v3/runtime/integration/StreamingStateIT.java b/community/community-it/bolt-it/src/test/java/org/neo4j/bolt/v3/runtime/integration/StreamingStateIT.java new file mode 100644 index 0000000000000..bcfc7dade33c2 --- /dev/null +++ b/community/community-it/bolt-it/src/test/java/org/neo4j/bolt/v3/runtime/integration/StreamingStateIT.java @@ -0,0 +1,273 @@ +/* + * Copyright (c) 2002-2018 "Neo4j," + * Neo4j Sweden AB [http://neo4j.com] + * + * This file is part of Neo4j. + * + * Neo4j is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ +package org.neo4j.bolt.v3.runtime.integration; + +import org.junit.jupiter.api.Test; +import org.junit.jupiter.params.ParameterizedTest; +import org.junit.jupiter.params.provider.MethodSource; + +import java.util.stream.Stream; + +import org.neo4j.bolt.messaging.BoltIOException; +import org.neo4j.bolt.messaging.RequestMessage; +import org.neo4j.bolt.runtime.BoltConnectionFatality; +import org.neo4j.bolt.runtime.BoltResponseHandler; +import org.neo4j.bolt.testing.BoltResponseRecorder; +import org.neo4j.bolt.testing.RecordedBoltResponse; +import org.neo4j.bolt.v1.messaging.request.DiscardAllMessage; +import org.neo4j.bolt.v1.messaging.request.InterruptSignal; +import org.neo4j.bolt.v1.messaging.request.PullAllMessage; +import org.neo4j.bolt.v1.messaging.request.ResetMessage; +import org.neo4j.bolt.v1.runtime.InterruptedState; +import org.neo4j.bolt.v3.BoltStateMachineV3; +import org.neo4j.bolt.v3.messaging.request.BeginMessage; +import org.neo4j.bolt.v3.messaging.request.RunMessage; +import org.neo4j.bolt.v3.runtime.FailedState; +import org.neo4j.bolt.v3.runtime.ReadyState; +import org.neo4j.bolt.v3.runtime.StreamingState; +import org.neo4j.bolt.v3.runtime.TransactionReadyState; +import org.neo4j.kernel.api.exceptions.Status; + +import static org.hamcrest.CoreMatchers.instanceOf; +import static org.hamcrest.MatcherAssert.assertThat; +import static org.junit.jupiter.api.Assertions.assertTrue; +import static org.mockito.ArgumentMatchers.any; +import static org.mockito.ArgumentMatchers.anyBoolean; +import static org.mockito.Mockito.doThrow; +import static org.mockito.Mockito.mock; +import static org.neo4j.bolt.testing.BoltMatchers.failedWithStatus; +import static org.neo4j.bolt.testing.BoltMatchers.succeeded; +import static org.neo4j.bolt.testing.BoltMatchers.verifyKillsConnection; +import static org.neo4j.bolt.testing.NullResponseHandler.nullResponseHandler; +import static org.neo4j.bolt.v3.messaging.request.CommitMessage.COMMIT_MESSAGE; +import static org.neo4j.bolt.v3.messaging.request.RollbackMessage.ROLLBACK_MESSAGE; + +class StreamingStateIT extends BoltStateMachineStateTestBase +{ + @Test + void shouldMoveFromStreamingToReadyOnPullAll_succ() throws Throwable + { + // Given + BoltStateMachineV3 machine = getBoltStateMachineInStreamingState(); + + // When + BoltResponseRecorder recorder = new BoltResponseRecorder(); + machine.process( PullAllMessage.INSTANCE, recorder ); + + // Then + RecordedBoltResponse response = recorder.nextResponse(); + assertThat( response, succeeded() ); + assertTrue( response.hasMetadata( "type" ) ); + assertTrue( response.hasMetadata( "result_consumed_after" ) ); + assertThat( machine.state(), instanceOf( ReadyState.class ) ); + } + + @Test + void shouldMoveFromStreamingToReadyOnDiscardAll_succ() throws Throwable + { + // Given + BoltStateMachineV3 machine = getBoltStateMachineInStreamingState(); + + // When + BoltResponseRecorder recorder = new BoltResponseRecorder(); + machine.process( DiscardAllMessage.INSTANCE, recorder ); + + // Then + assertThat( recorder.nextResponse(), succeeded() ); + assertThat( machine.state(), instanceOf( ReadyState.class ) ); + } + + @Test + void shouldMoveFromTxStreamingToTxReadyOnDiscardAll_succ() throws Throwable + { + // Given + BoltStateMachineV3 machine = getBoltStateMachineInTxStreamingState(); + + // When + BoltResponseRecorder recorder = new BoltResponseRecorder(); + machine.process( DiscardAllMessage.INSTANCE, recorder ); + + // Then + assertThat( recorder.nextResponse(), succeeded() ); + assertThat( machine.state(), instanceOf( TransactionReadyState.class ) ); + } + + @Test + void shouldMoveFromTxStreamingToTxReadyOnPullAll_succ() throws Throwable + { + // Given + BoltStateMachineV3 machine = getBoltStateMachineInTxStreamingState(); + + // When + BoltResponseRecorder recorder = new BoltResponseRecorder(); + machine.process( PullAllMessage.INSTANCE, recorder ); + + // Then + RecordedBoltResponse response = recorder.nextResponse(); + assertThat( response, succeeded() ); + assertTrue( response.hasMetadata( "type" ) ); + assertTrue( response.hasMetadata( "result_consumed_after" ) ); + assertThat( machine.state(), instanceOf( TransactionReadyState.class ) ); + } + + @Test + void shouldMoveFromStreamingToInterruptedOnInterrupt() throws Throwable + { + // Given + BoltStateMachineV3 machine = getBoltStateMachineInStreamingState(); + + // When + BoltResponseRecorder recorder = new BoltResponseRecorder(); + machine.process( InterruptSignal.INSTANCE, recorder ); + + // Then + assertThat( machine.state(), instanceOf( InterruptedState.class ) ); + } + + @Test + void shouldMoveFromTxStreamingToInterruptedOnInterrupt() throws Throwable + { + // Given + BoltStateMachineV3 machine = getBoltStateMachineInTxStreamingState(); + + // When + BoltResponseRecorder recorder = new BoltResponseRecorder(); + machine.process( InterruptSignal.INSTANCE, recorder ); + + // Then + assertThat( machine.state(), instanceOf( InterruptedState.class ) ); + } + + @ParameterizedTest + @MethodSource( "pullAllDiscardAllMessages" ) + void shouldMoveFromStreamingStateToFailedStateOnPullAllOrDiscardAll_fail( RequestMessage message ) throws Throwable + { + // Given + BoltStateMachineV3 machine = getBoltStateMachineInStreamingState(); + + // When + BoltResponseHandler handler = mock( BoltResponseHandler.class ); + doThrow( new RuntimeException( "Fail" ) ).when( handler ).onRecords( any(), anyBoolean() ); + machine.process( message, handler ); + + // Then + assertThat( machine.state(), instanceOf( FailedState.class ) ); + } + + @ParameterizedTest + @MethodSource( "pullAllDiscardAllMessages" ) + void shouldMoveFromTxStreamingStateToFailedStateOnPullAllOrDiscardAll_fail( RequestMessage message ) throws Throwable + { + // Given + BoltStateMachineV3 machine = getBoltStateMachineInTxStreamingState(); + + // When + + BoltResponseHandler handler = mock( BoltResponseHandler.class ); + doThrow( new RuntimeException( "Fail" ) ).when( handler ).onRecords( any(), anyBoolean() ); + machine.process( message, handler ); + + // Then + assertThat( machine.state(), instanceOf( FailedState.class ) ); + } + + @ParameterizedTest + @MethodSource( "illegalV3Messages" ) + void shouldCloseConnectionOnIllegalV3MessagesInStreamingState( RequestMessage message ) throws Throwable + { + shouldThrowExceptionOnIllegalMessagesInStreamingState( message ); + } + + @ParameterizedTest + @MethodSource( "illegalV3Messages" ) + void shouldCloseConnectionOnIllegalV3MessagesInTxStreamingState( RequestMessage message ) throws Throwable + { + shouldThrowExceptionOnIllegalMessagesInTxStreamingState( message ); + } + + private void shouldThrowExceptionOnIllegalMessagesInStreamingState( RequestMessage message ) throws Throwable + { + // Given + BoltStateMachineV3 machine = newStateMachine(); + machine.process( newHelloMessage(), nullResponseHandler() ); + + machine.process( new RunMessage( "CREATE (n {k:'k'}) RETURN n.k", EMPTY_PARAMS ), nullResponseHandler() ); + assertThat( machine.state(), instanceOf( StreamingState.class ) ); + + // when + BoltResponseRecorder recorder = new BoltResponseRecorder(); + verifyKillsConnection( () -> machine.process( message, recorder ) ); + + // then + assertThat( recorder.nextResponse(), failedWithStatus( Status.Request.Invalid ) ); + assertThat( machine.state(), instanceOf( StreamingState.class ) ); + } + + private void shouldThrowExceptionOnIllegalMessagesInTxStreamingState( RequestMessage message ) throws Throwable + { + // Given + BoltStateMachineV3 machine = newStateMachine(); + machine.process( newHelloMessage(), nullResponseHandler() ); + + machine.process( new BeginMessage(), nullResponseHandler() ); + machine.process( new RunMessage( "CREATE (n {k:'k'}) RETURN n.k", EMPTY_PARAMS ), nullResponseHandler() ); + assertThat( machine.state(), instanceOf( StreamingState.class ) ); + + // when + BoltResponseRecorder recorder = new BoltResponseRecorder(); + verifyKillsConnection( () -> machine.process( message, recorder ) ); + + // then + assertThat( recorder.nextResponse(), failedWithStatus( Status.Request.Invalid ) ); + assertThat( machine.state(), instanceOf( StreamingState.class ) ); + } + + private static Stream illegalV3Messages() throws BoltIOException + { + return Stream.of( newHelloMessage(), new RunMessage( "any string" ), new BeginMessage(), ROLLBACK_MESSAGE, COMMIT_MESSAGE, ResetMessage.INSTANCE ); + } + + private static Stream pullAllDiscardAllMessages() + { + return Stream.of( PullAllMessage.INSTANCE, DiscardAllMessage.INSTANCE ); + } + + private BoltStateMachineV3 getBoltStateMachineInStreamingState() throws BoltConnectionFatality, BoltIOException + { + BoltStateMachineV3 machine = newStateMachine(); + machine.process( newHelloMessage(), nullResponseHandler() ); + + machine.process( new RunMessage( "CREATE (n {k:'k'}) RETURN n.k", EMPTY_PARAMS ), nullResponseHandler() ); + assertThat( machine.state(), instanceOf( StreamingState.class ) ); + return machine; + } + + private BoltStateMachineV3 getBoltStateMachineInTxStreamingState() throws BoltConnectionFatality, BoltIOException + { + BoltStateMachineV3 machine = newStateMachine(); + machine.process( newHelloMessage(), nullResponseHandler() ); + + machine.process( new BeginMessage(), nullResponseHandler() ); + assertThat( machine.state(), instanceOf( TransactionReadyState.class ) ); + machine.process( new RunMessage( "CREATE (n {k:'k'}) RETURN n.k", EMPTY_PARAMS ), nullResponseHandler() ); + assertThat( machine.state(), instanceOf( StreamingState.class ) ); // tx streaming state + return machine; + } +} diff --git a/community/community-it/bolt-it/src/test/java/org/neo4j/bolt/v3/runtime/integration/TransactionReadyStateIT.java b/community/community-it/bolt-it/src/test/java/org/neo4j/bolt/v3/runtime/integration/TransactionReadyStateIT.java new file mode 100644 index 0000000000000..6c2fd23972368 --- /dev/null +++ b/community/community-it/bolt-it/src/test/java/org/neo4j/bolt/v3/runtime/integration/TransactionReadyStateIT.java @@ -0,0 +1,171 @@ +/* + * Copyright (c) 2002-2018 "Neo4j," + * Neo4j Sweden AB [http://neo4j.com] + * + * This file is part of Neo4j. + * + * Neo4j is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ +package org.neo4j.bolt.v3.runtime.integration; + +import org.junit.jupiter.api.Test; +import org.junit.jupiter.params.ParameterizedTest; +import org.junit.jupiter.params.provider.MethodSource; + +import java.util.stream.Stream; + +import org.neo4j.bolt.messaging.BoltIOException; +import org.neo4j.bolt.messaging.RequestMessage; +import org.neo4j.bolt.runtime.BoltConnectionFatality; +import org.neo4j.bolt.runtime.BoltResponseHandler; +import org.neo4j.bolt.testing.BoltResponseRecorder; +import org.neo4j.bolt.testing.RecordedBoltResponse; +import org.neo4j.bolt.v1.messaging.request.DiscardAllMessage; +import org.neo4j.bolt.v1.messaging.request.InterruptSignal; +import org.neo4j.bolt.v1.messaging.request.PullAllMessage; +import org.neo4j.bolt.v1.runtime.InterruptedState; +import org.neo4j.bolt.v3.BoltStateMachineV3; +import org.neo4j.bolt.v3.messaging.request.BeginMessage; +import org.neo4j.bolt.v3.messaging.request.RunMessage; +import org.neo4j.bolt.v3.runtime.FailedState; +import org.neo4j.bolt.v3.runtime.ReadyState; +import org.neo4j.bolt.v3.runtime.StreamingState; +import org.neo4j.bolt.v3.runtime.TransactionReadyState; +import org.neo4j.kernel.api.exceptions.Status; + +import static org.hamcrest.CoreMatchers.instanceOf; +import static org.hamcrest.MatcherAssert.assertThat; +import static org.junit.Assert.assertFalse; +import static org.junit.jupiter.api.Assertions.assertTrue; +import static org.mockito.ArgumentMatchers.any; +import static org.mockito.ArgumentMatchers.anyBoolean; +import static org.mockito.Mockito.doThrow; +import static org.mockito.Mockito.mock; +import static org.neo4j.bolt.testing.BoltMatchers.failedWithStatus; +import static org.neo4j.bolt.testing.BoltMatchers.succeeded; +import static org.neo4j.bolt.testing.BoltMatchers.verifyKillsConnection; +import static org.neo4j.bolt.testing.NullResponseHandler.nullResponseHandler; +import static org.neo4j.bolt.v3.messaging.request.CommitMessage.COMMIT_MESSAGE; +import static org.neo4j.bolt.v3.messaging.request.RollbackMessage.ROLLBACK_MESSAGE; + +class TransactionReadyStateIT extends BoltStateMachineStateTestBase +{ + @Test + void shouldMoveToStreamingOnRun_succ() throws Throwable + { + // Given + BoltStateMachineV3 machine = getBoltStateMachineInTxReadyState(); + + // When + BoltResponseRecorder recorder = new BoltResponseRecorder(); + machine.process( new RunMessage( "CREATE (n {k:'k'}) RETURN n.k", EMPTY_PARAMS ), recorder ); + + // Then + RecordedBoltResponse response = recorder.nextResponse(); + assertTrue( response.hasMetadata( "fields" ) ); + assertTrue( response.hasMetadata( "result_available_after" ) ); + assertFalse( response.hasMetadata( "tx_id" ) ); + assertThat( machine.state(), instanceOf( StreamingState.class ) ); + } + + @Test + void shouldMoveToReadyOnCommit_succ() throws Throwable + { + BoltStateMachineV3 machine = getBoltStateMachineInTxReadyState(); + + // When + BoltResponseRecorder recorder = new BoltResponseRecorder(); + machine.process( COMMIT_MESSAGE, recorder ); + + // Then + RecordedBoltResponse response = recorder.nextResponse(); + assertThat( response, succeeded() ); + assertTrue( response.hasMetadata( "bookmark" ) ); + assertThat( machine.state(), instanceOf( ReadyState.class ) ); + } + + @Test + void shouldMoveToReadyOnRollback_succ() throws Throwable + { + BoltStateMachineV3 machine = getBoltStateMachineInTxReadyState(); + + // When + BoltResponseRecorder recorder = new BoltResponseRecorder(); + machine.process( ROLLBACK_MESSAGE, recorder ); + + // Then + assertThat( recorder.nextResponse(), succeeded() ); + assertThat( machine.state(), instanceOf( ReadyState.class ) ); + } + + @Test + void shouldMoveToFailedOnRun_fail() throws Throwable + { + BoltStateMachineV3 machine = getBoltStateMachineInTxReadyState(); + + // When + BoltResponseHandler handler = mock( BoltResponseHandler.class ); + doThrow( new RuntimeException( "Error!" ) ).when( handler ).onRecords( any(), anyBoolean() ); + machine.process( new RunMessage( "A cypher query" ), handler ); + + // Then + assertThat( machine.state(), instanceOf( FailedState.class ) ); + } + + @Test + void shouldMoveToInterruptedOnInterrupt() throws Throwable + { + // Given + BoltStateMachineV3 machine = getBoltStateMachineInTxReadyState(); + // When + BoltResponseRecorder recorder = new BoltResponseRecorder(); + machine.process( InterruptSignal.INSTANCE, recorder ); + + // Then + assertThat( machine.state(), instanceOf( InterruptedState.class ) ); + } + + @ParameterizedTest + @MethodSource( "illegalV3Messages" ) + void shouldCloseConnectionOnIllegalV3Messages( RequestMessage message ) throws Throwable + { + shouldCloseConnectionOnIllegalMessages( message ); + } + + private void shouldCloseConnectionOnIllegalMessages( RequestMessage message ) throws Throwable + { + // Given + BoltStateMachineV3 machine = getBoltStateMachineInTxReadyState(); + // when + BoltResponseRecorder recorder = new BoltResponseRecorder(); + verifyKillsConnection( () -> machine.process( message, recorder ) ); + + // then + assertThat( recorder.nextResponse(), failedWithStatus( Status.Request.Invalid ) ); + assertThat( machine.state(), instanceOf( TransactionReadyState.class ) ); + } + + private static Stream illegalV3Messages() throws BoltIOException + { + return Stream.of( newHelloMessage(), DiscardAllMessage.INSTANCE, PullAllMessage.INSTANCE, new BeginMessage() ); + } + + private BoltStateMachineV3 getBoltStateMachineInTxReadyState() throws BoltConnectionFatality, BoltIOException + { + BoltStateMachineV3 machine = newStateMachine(); + machine.process( newHelloMessage(), nullResponseHandler() ); + machine.process( new BeginMessage(), nullResponseHandler() ); + return machine; + } +}