diff --git a/driver/src/main/java/org/neo4j/driver/Statement.java b/driver/src/main/java/org/neo4j/driver/Statement.java index 5e6ec5f39c..fea533f606 100644 --- a/driver/src/main/java/org/neo4j/driver/Statement.java +++ b/driver/src/main/java/org/neo4j/driver/Statement.java @@ -36,7 +36,7 @@ * @see Session * @see Transaction * @see StatementResult - * @see StatementResult#summary() + * @see StatementResult#consume() * @see ResultSummary * @since 1.0 */ diff --git a/driver/src/main/java/org/neo4j/driver/StatementResult.java b/driver/src/main/java/org/neo4j/driver/StatementResult.java index 1feb142ead..90e33fccb9 100644 --- a/driver/src/main/java/org/neo4j/driver/StatementResult.java +++ b/driver/src/main/java/org/neo4j/driver/StatementResult.java @@ -147,5 +147,5 @@ public interface StatementResult extends Iterator * * @return a summary for the whole query result. */ - ResultSummary summary(); + ResultSummary consume(); } diff --git a/driver/src/main/java/org/neo4j/driver/StatementRunner.java b/driver/src/main/java/org/neo4j/driver/StatementRunner.java index de95780d64..0ec6445a81 100644 --- a/driver/src/main/java/org/neo4j/driver/StatementRunner.java +++ b/driver/src/main/java/org/neo4j/driver/StatementRunner.java @@ -42,7 +42,7 @@ * * diff --git a/driver/src/main/java/org/neo4j/driver/async/AsyncStatementRunner.java b/driver/src/main/java/org/neo4j/driver/async/AsyncStatementRunner.java index d00ca07ac8..cf6aa2c49a 100644 --- a/driver/src/main/java/org/neo4j/driver/async/AsyncStatementRunner.java +++ b/driver/src/main/java/org/neo4j/driver/async/AsyncStatementRunner.java @@ -51,7 +51,7 @@ * * diff --git a/driver/src/main/java/org/neo4j/driver/async/StatementResultCursor.java b/driver/src/main/java/org/neo4j/driver/async/StatementResultCursor.java index c1a6763503..8e8f29ad39 100644 --- a/driver/src/main/java/org/neo4j/driver/async/StatementResultCursor.java +++ b/driver/src/main/java/org/neo4j/driver/async/StatementResultCursor.java @@ -80,7 +80,7 @@ public interface StatementResultCursor * @return a {@link CompletionStage} completed with a summary for the whole query result. Stage can also be * completed exceptionally if query execution fails. */ - CompletionStage summaryAsync(); + CompletionStage consumeAsync(); /** * Asynchronously navigate to and retrieve the next {@link Record} in this result. Returned stage can contain diff --git a/driver/src/main/java/org/neo4j/driver/exceptions/ResultConsumedException.java b/driver/src/main/java/org/neo4j/driver/exceptions/ResultConsumedException.java new file mode 100644 index 0000000000..f250b22388 --- /dev/null +++ b/driver/src/main/java/org/neo4j/driver/exceptions/ResultConsumedException.java @@ -0,0 +1,34 @@ +/* + * Copyright (c) 2002-2019 "Neo4j," + * Neo4j Sweden AB [http://neo4j.com] + * + * This file is part of Neo4j. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.neo4j.driver.exceptions; + +import org.neo4j.driver.StatementRunner; + +/** + * A user is trying to access resources that are no longer valid due to + * the resources have already been consumed or + * the {@link StatementRunner} where the resources are created has already been closed. + */ +public class ResultConsumedException extends ClientException +{ + public ResultConsumedException( String message ) + { + super( message ); + } +} diff --git a/driver/src/main/java/org/neo4j/driver/exceptions/TransactionNestingException.java b/driver/src/main/java/org/neo4j/driver/exceptions/TransactionNestingException.java new file mode 100644 index 0000000000..312a9e7c7f --- /dev/null +++ b/driver/src/main/java/org/neo4j/driver/exceptions/TransactionNestingException.java @@ -0,0 +1,30 @@ +/* + * Copyright (c) 2002-2019 "Neo4j," + * Neo4j Sweden AB [http://neo4j.com] + * + * This file is part of Neo4j. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.neo4j.driver.exceptions; + +/** + * This exception indicates a user is nesting new transaction with a on-going transaction (explicit and/or auto-commit). + */ +public class TransactionNestingException extends ClientException +{ + public TransactionNestingException( String message ) + { + super( message ); + } +} diff --git a/driver/src/main/java/org/neo4j/driver/internal/FailableCursor.java b/driver/src/main/java/org/neo4j/driver/internal/FailableCursor.java index 916572b4ad..058384879a 100644 --- a/driver/src/main/java/org/neo4j/driver/internal/FailableCursor.java +++ b/driver/src/main/java/org/neo4j/driver/internal/FailableCursor.java @@ -22,6 +22,13 @@ public interface FailableCursor { - CompletionStage consumeAsync(); - CompletionStage failureAsync(); + /** + * Discarding all unconsumed records and returning failure if there is any to run and/or pulls. + */ + CompletionStage discardAllFailureAsync(); + + /** + * Pulling all unconsumed records into memory and returning failure if there is any to run and/or pulls. + */ + CompletionStage pullAllFailureAsync(); } diff --git a/driver/src/main/java/org/neo4j/driver/internal/InternalStatementResult.java b/driver/src/main/java/org/neo4j/driver/internal/InternalStatementResult.java index bc2532346e..3e312721cd 100644 --- a/driver/src/main/java/org/neo4j/driver/internal/InternalStatementResult.java +++ b/driver/src/main/java/org/neo4j/driver/internal/InternalStatementResult.java @@ -112,9 +112,9 @@ public List list( Function mapFunction ) } @Override - public ResultSummary summary() + public ResultSummary consume() { - return blockingGet( cursor.summaryAsync() ); + return blockingGet( cursor.consumeAsync() ); } @Override diff --git a/driver/src/main/java/org/neo4j/driver/internal/async/NetworkSession.java b/driver/src/main/java/org/neo4j/driver/internal/async/NetworkSession.java index e0c3632ee7..02f9eb4190 100644 --- a/driver/src/main/java/org/neo4j/driver/internal/async/NetworkSession.java +++ b/driver/src/main/java/org/neo4j/driver/internal/async/NetworkSession.java @@ -38,6 +38,7 @@ import org.neo4j.driver.internal.cursor.RxStatementResultCursor; import org.neo4j.driver.internal.cursor.StatementResultCursorFactory; import org.neo4j.driver.internal.logging.PrefixedLogger; +import org.neo4j.driver.exceptions.TransactionNestingException; import org.neo4j.driver.internal.retry.RetryLogic; import org.neo4j.driver.internal.spi.Connection; import org.neo4j.driver.internal.spi.ConnectionProvider; @@ -196,7 +197,7 @@ public CompletionStage closeAsync() if ( cursor != null ) { // there exists a cursor with potentially unconsumed error, try to extract and propagate it - return cursor.consumeAsync(); + return cursor.discardAllFailureAsync(); } // no result cursor exists so no error exists return completedWithNull(); @@ -254,7 +255,7 @@ private CompletionStage acquireConnection( AccessMode mode ) return completedWithNull(); } // make sure previous result is fully consumed and connection is released back to the pool - return cursor.failureAsync(); + return cursor.pullAllFailureAsync(); } ).thenCompose( error -> { if ( error == null ) @@ -323,7 +324,7 @@ private CompletionStage ensureNoOpenTx( String errorMessage ) { if ( tx != null ) { - throw new ClientException( errorMessage ); + throw new TransactionNestingException( errorMessage ); } } ); } diff --git a/driver/src/main/java/org/neo4j/driver/internal/async/ResultCursorsHolder.java b/driver/src/main/java/org/neo4j/driver/internal/async/ResultCursorsHolder.java index 355c16ad25..199f4a3981 100644 --- a/driver/src/main/java/org/neo4j/driver/internal/async/ResultCursorsHolder.java +++ b/driver/src/main/java/org/neo4j/driver/internal/async/ResultCursorsHolder.java @@ -74,6 +74,6 @@ private static CompletionStage retrieveFailure( CompletionStage null ) - .thenCompose( cursor -> cursor == null ? completedWithNull() : cursor.consumeAsync() ); + .thenCompose( cursor -> cursor == null ? completedWithNull() : cursor.discardAllFailureAsync() ); } } diff --git a/driver/src/main/java/org/neo4j/driver/internal/cursor/AsyncStatementResultCursorImpl.java b/driver/src/main/java/org/neo4j/driver/internal/cursor/AsyncStatementResultCursorImpl.java index 97ba5cd031..6a0eb7d336 100644 --- a/driver/src/main/java/org/neo4j/driver/internal/cursor/AsyncStatementResultCursorImpl.java +++ b/driver/src/main/java/org/neo4j/driver/internal/cursor/AsyncStatementResultCursorImpl.java @@ -49,9 +49,9 @@ public List keys() } @Override - public CompletionStage summaryAsync() + public CompletionStage consumeAsync() { - return pullAllHandler.summaryAsync(); + return pullAllHandler.consumeAsync(); } @Override @@ -95,7 +95,7 @@ public CompletionStage forEachAsync( Consumer action ) { CompletableFuture resultFuture = new CompletableFuture<>(); internalForEachAsync( action, resultFuture ); - return resultFuture.thenCompose( ignore -> summaryAsync() ); + return resultFuture.thenCompose( ignore -> consumeAsync() ); } @Override @@ -111,18 +111,17 @@ public CompletionStage> listAsync( Function mapFunction ) } @Override - public CompletionStage consumeAsync() + public CompletionStage discardAllFailureAsync() { - return pullAllHandler.summaryAsync().handle( ( summary, error ) -> error ); + return consumeAsync().handle( ( summary, error ) -> error ); } @Override - public CompletionStage failureAsync() + public CompletionStage pullAllFailureAsync() { - return pullAllHandler.failureAsync(); + return pullAllHandler.pullAllFailureAsync(); } - private void internalForEachAsync( Consumer action, CompletableFuture resultFuture ) { CompletionStage recordFuture = nextAsync(); diff --git a/driver/src/main/java/org/neo4j/driver/internal/cursor/AsyncStatementResultCursorOnlyFactory.java b/driver/src/main/java/org/neo4j/driver/internal/cursor/AsyncStatementResultCursorOnlyFactory.java index 6752e0a249..e84313c12f 100644 --- a/driver/src/main/java/org/neo4j/driver/internal/cursor/AsyncStatementResultCursorOnlyFactory.java +++ b/driver/src/main/java/org/neo4j/driver/internal/cursor/AsyncStatementResultCursorOnlyFactory.java @@ -66,11 +66,12 @@ public CompletionStage asyncResult() if ( waitForRunResponse ) { // wait for response of RUN before proceeding - return runHandler.runFuture().thenApply( ignore -> new AsyncStatementResultCursorImpl( runHandler, pullAllHandler ) ); + return runHandler.runFuture().thenApply( ignore -> + new DisposableAsyncStatementResultCursor( new AsyncStatementResultCursorImpl( runHandler, pullAllHandler ) ) ); } else { - return completedFuture( new AsyncStatementResultCursorImpl( runHandler, pullAllHandler ) ); + return completedFuture( new DisposableAsyncStatementResultCursor( new AsyncStatementResultCursorImpl( runHandler, pullAllHandler ) ) ); } } diff --git a/driver/src/main/java/org/neo4j/driver/internal/cursor/DisposableAsyncStatementResultCursor.java b/driver/src/main/java/org/neo4j/driver/internal/cursor/DisposableAsyncStatementResultCursor.java new file mode 100644 index 0000000000..96d9406861 --- /dev/null +++ b/driver/src/main/java/org/neo4j/driver/internal/cursor/DisposableAsyncStatementResultCursor.java @@ -0,0 +1,121 @@ +/* + * Copyright (c) 2002-2019 "Neo4j," + * Neo4j Sweden AB [http://neo4j.com] + * + * This file is part of Neo4j. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.neo4j.driver.internal.cursor; + +import java.util.List; +import java.util.concurrent.CompletableFuture; +import java.util.concurrent.CompletionStage; +import java.util.function.Consumer; +import java.util.function.Function; + +import org.neo4j.driver.Record; +import org.neo4j.driver.summary.ResultSummary; + +import static org.neo4j.driver.internal.util.ErrorUtil.newResultConsumedError; +import static org.neo4j.driver.internal.util.Futures.completedWithNull; +import static org.neo4j.driver.internal.util.Futures.failedFuture; + +public class DisposableAsyncStatementResultCursor implements AsyncStatementResultCursor +{ + private final AsyncStatementResultCursor delegate; + private boolean isDisposed; + + public DisposableAsyncStatementResultCursor( AsyncStatementResultCursor delegate ) + { + this.delegate = delegate; + } + + @Override + public List keys() + { + return delegate.keys(); + } + + @Override + public CompletionStage consumeAsync() + { + isDisposed = true; + return delegate.consumeAsync(); + } + + @Override + public CompletionStage nextAsync() + { + return assertNotDisposed().thenCompose( ignored -> delegate.nextAsync() ); + } + + @Override + public CompletionStage peekAsync() + { + return assertNotDisposed().thenCompose( ignored -> delegate.peekAsync() ); + } + + @Override + public CompletionStage singleAsync() + { + return assertNotDisposed().thenCompose( ignored -> delegate.singleAsync() ); + } + + @Override + public CompletionStage forEachAsync( Consumer action ) + { + return assertNotDisposed().thenCompose( ignored -> delegate.forEachAsync( action ) ); + } + + @Override + public CompletionStage> listAsync() + { + return assertNotDisposed().thenCompose( ignored -> delegate.listAsync() ); + } + + @Override + public CompletionStage> listAsync( Function mapFunction ) + { + return assertNotDisposed().thenCompose( ignored -> delegate.listAsync( mapFunction ) ); + } + + @Override + public CompletionStage discardAllFailureAsync() + { + isDisposed = true; + return delegate.discardAllFailureAsync(); + } + + @Override + public CompletionStage pullAllFailureAsync() + { + // This one does not dispose the result so that a user could still visit the buffered result after this method call. + // This also does not assert not disposed so that this method can be called after summary. + return delegate.pullAllFailureAsync(); + } + + private CompletableFuture assertNotDisposed() + { + if ( isDisposed ) + { + return failedFuture( newResultConsumedError() ); + } + return completedWithNull(); + } + + boolean isDisposed() + { + return this.isDisposed; + } +} diff --git a/driver/src/main/java/org/neo4j/driver/internal/cursor/RxStatementResultCursorImpl.java b/driver/src/main/java/org/neo4j/driver/internal/cursor/RxStatementResultCursorImpl.java index 689320a7c0..0877dbcb1f 100644 --- a/driver/src/main/java/org/neo4j/driver/internal/cursor/RxStatementResultCursorImpl.java +++ b/driver/src/main/java/org/neo4j/driver/internal/cursor/RxStatementResultCursorImpl.java @@ -25,10 +25,13 @@ import java.util.function.BiConsumer; import org.neo4j.driver.Record; +import org.neo4j.driver.exceptions.TransactionNestingException; import org.neo4j.driver.internal.handlers.RunResponseHandler; import org.neo4j.driver.internal.handlers.pulln.PullResponseHandler; import org.neo4j.driver.summary.ResultSummary; +import static org.neo4j.driver.internal.util.ErrorUtil.newResultConsumedError; + public class RxStatementResultCursorImpl implements RxStatementResultCursor { static final BiConsumer DISCARD_RECORD_CONSUMER = ( record, throwable ) -> {/*do nothing*/}; @@ -37,6 +40,7 @@ public class RxStatementResultCursorImpl implements RxStatementResultCursor private final Throwable runResponseError; private final CompletableFuture summaryFuture = new CompletableFuture<>(); private BiConsumer recordConsumer; + private boolean resultConsumed; public RxStatementResultCursorImpl( RunResponseHandler runHandler, PullResponseHandler pullHandler ) { @@ -64,6 +68,10 @@ public List keys() @Override public void installRecordConsumer( BiConsumer recordConsumer ) { + if ( resultConsumed ) + { + throw newResultConsumedError(); + } if ( isRecordConsumerInstalled() ) { return; @@ -91,28 +99,33 @@ public void cancel() } @Override - public CompletionStage consumeAsync() + public CompletionStage discardAllFailureAsync() { // calling this method will enforce discarding record stream and finish running cypher query return summaryAsync().thenApply( summary -> (Throwable) null ).exceptionally( error -> error ); } @Override - public CompletionStage failureAsync() + public CompletionStage pullAllFailureAsync() { + if ( isRecordConsumerInstalled() && !isDone() ) + { + return CompletableFuture.completedFuture( new TransactionNestingException( + "You cannot run another query or begin a new transaction in the same session before you've fully consumed the previous run result." ) ); + } // It is safe to discard records as either the streaming has not started at all, or the streaming is fully finished. - return consumeAsync(); + return discardAllFailureAsync(); } @Override public CompletionStage summaryAsync() { - if ( !isDone() ) // the summary is called before record streaming + if ( !isDone() && !resultConsumed ) // the summary is called before record streaming { installRecordConsumer( DISCARD_RECORD_CONSUMER ); cancel(); + resultConsumed = true; } - return this.summaryFuture; } diff --git a/driver/src/main/java/org/neo4j/driver/internal/cursor/StatementResultCursorFactoryImpl.java b/driver/src/main/java/org/neo4j/driver/internal/cursor/StatementResultCursorFactoryImpl.java index 5259bc26cb..fe8a263787 100644 --- a/driver/src/main/java/org/neo4j/driver/internal/cursor/StatementResultCursorFactoryImpl.java +++ b/driver/src/main/java/org/neo4j/driver/internal/cursor/StatementResultCursorFactoryImpl.java @@ -69,11 +69,12 @@ public CompletionStage asyncResult() if ( waitForRunResponse ) { // wait for response of RUN before proceeding - return runHandler.runFuture().thenApply( ignore -> new AsyncStatementResultCursorImpl( runHandler, pullAllHandler ) ); + return runHandler.runFuture().thenApply( + ignore -> new DisposableAsyncStatementResultCursor( new AsyncStatementResultCursorImpl( runHandler, pullAllHandler ) ) ); } else { - return completedFuture( new AsyncStatementResultCursorImpl( runHandler, pullAllHandler ) ); + return completedFuture( new DisposableAsyncStatementResultCursor( new AsyncStatementResultCursorImpl( runHandler, pullAllHandler ) ) ); } } diff --git a/driver/src/main/java/org/neo4j/driver/internal/handlers/LegacyPullAllResponseHandler.java b/driver/src/main/java/org/neo4j/driver/internal/handlers/LegacyPullAllResponseHandler.java index 00c04dfb05..36d507d544 100644 --- a/driver/src/main/java/org/neo4j/driver/internal/handlers/LegacyPullAllResponseHandler.java +++ b/driver/src/main/java/org/neo4j/driver/internal/handlers/LegacyPullAllResponseHandler.java @@ -178,11 +178,11 @@ public synchronized CompletionStage nextAsync() return peekAsync().thenApply( ignore -> dequeueRecord() ); } - public synchronized CompletionStage summaryAsync() + public synchronized CompletionStage consumeAsync() { ignoreRecords = true; records.clear(); - return failureAsync().thenApply( error -> + return pullAllFailureAsync().thenApply( error -> { if ( error != null ) { @@ -194,7 +194,7 @@ public synchronized CompletionStage summaryAsync() public synchronized CompletionStage> listAsync( Function mapFunction ) { - return failureAsync().thenApply( error -> + return pullAllFailureAsync().thenApply( error -> { if ( error != null ) { @@ -210,7 +210,7 @@ public void prePopulateRecords() connection.writeAndFlush( PullAllMessage.PULL_ALL, this ); } - public synchronized CompletionStage failureAsync() + public synchronized CompletionStage pullAllFailureAsync() { if ( failure != null ) { diff --git a/driver/src/main/java/org/neo4j/driver/internal/handlers/PullAllResponseHandler.java b/driver/src/main/java/org/neo4j/driver/internal/handlers/PullAllResponseHandler.java index 05466cc5db..b8001506fa 100644 --- a/driver/src/main/java/org/neo4j/driver/internal/handlers/PullAllResponseHandler.java +++ b/driver/src/main/java/org/neo4j/driver/internal/handlers/PullAllResponseHandler.java @@ -28,7 +28,7 @@ public interface PullAllResponseHandler extends ResponseHandler { - CompletionStage summaryAsync(); + CompletionStage consumeAsync(); CompletionStage nextAsync(); @@ -36,7 +36,7 @@ public interface PullAllResponseHandler extends ResponseHandler CompletionStage> listAsync( Function mapFunction ); - CompletionStage failureAsync(); + CompletionStage pullAllFailureAsync(); void prePopulateRecords(); } diff --git a/driver/src/main/java/org/neo4j/driver/internal/handlers/pulln/AutoPullResponseHandler.java b/driver/src/main/java/org/neo4j/driver/internal/handlers/pulln/AutoPullResponseHandler.java index 9aaea3009f..65eed3f086 100644 --- a/driver/src/main/java/org/neo4j/driver/internal/handlers/pulln/AutoPullResponseHandler.java +++ b/driver/src/main/java/org/neo4j/driver/internal/handlers/pulln/AutoPullResponseHandler.java @@ -137,17 +137,16 @@ public synchronized CompletionStage nextAsync() return peekAsync().thenApply( ignore -> dequeueRecord() ); } - public synchronized CompletionStage summaryAsync() + public synchronized CompletionStage consumeAsync() { + records.clear(); if ( isDone() ) { - records.clear(); return completedWithValueIfNoFailure( summary ); } else { cancel(); - records.clear(); if ( summaryFuture == null ) { summaryFuture = new CompletableFuture<>(); @@ -163,7 +162,7 @@ public synchronized CompletionStage> listAsync( Function m } @Override - public synchronized CompletionStage failureAsync() + public synchronized CompletionStage pullAllFailureAsync() { return pullAllAsync().handle( ( ignore, error ) -> error ); } diff --git a/driver/src/main/java/org/neo4j/driver/internal/reactive/InternalRxSession.java b/driver/src/main/java/org/neo4j/driver/internal/reactive/InternalRxSession.java index 07b54a0cc6..da04440ede 100644 --- a/driver/src/main/java/org/neo4j/driver/internal/reactive/InternalRxSession.java +++ b/driver/src/main/java/org/neo4j/driver/internal/reactive/InternalRxSession.java @@ -28,6 +28,7 @@ import org.neo4j.driver.Statement; import org.neo4j.driver.TransactionConfig; import org.neo4j.driver.Bookmark; +import org.neo4j.driver.exceptions.TransactionNestingException; import org.neo4j.driver.internal.async.NetworkSession; import org.neo4j.driver.internal.cursor.RxStatementResultCursor; import org.neo4j.driver.internal.util.Futures; @@ -122,7 +123,8 @@ public Publisher writeTransaction( RxTransactionWork> work, private Publisher runTransaction( AccessMode mode, RxTransactionWork> work, TransactionConfig config ) { - Flux repeatableWork = Flux.usingWhen( beginTransaction( mode, config ), work::execute, RxTransaction::commit, RxTransaction::rollback ); + Flux repeatableWork = Flux.usingWhen( beginTransaction( mode, config ), work::execute, + RxTransaction::commit, ( tx, error ) -> tx.rollback(), null ); return session.retryLogic().retryRx( repeatableWork ); } @@ -168,11 +170,18 @@ private void releaseConnectionBeforeReturning( CompletableFuture returnFu // We failed to create a result cursor so we cannot rely on result cursor to cleanup resources. // Therefore we will first release the connection that might have been created in the session and then notify the error. // The logic here shall be the same as `SessionPullResponseHandler#afterFailure`. - // The reason we need to release connection in session is that we do not have a `rxSession.close()`; + // The reason we need to release connection in session is that we made `rxSession.close()` optional; // Otherwise, session.close shall handle everything for us. Throwable error = Futures.completionExceptionCause( completionError ); - session.releaseConnectionAsync().whenComplete( ( ignored, closeError ) -> - returnFuture.completeExceptionally( Futures.combineErrors( error, closeError ) ) ); + if ( error instanceof TransactionNestingException ) + { + returnFuture.completeExceptionally( error ); + } + else + { + session.releaseConnectionAsync().whenComplete( ( ignored, closeError ) -> + returnFuture.completeExceptionally( Futures.combineErrors( error, closeError ) ) ); + } } @Override diff --git a/driver/src/main/java/org/neo4j/driver/internal/reactive/InternalRxStatementResult.java b/driver/src/main/java/org/neo4j/driver/internal/reactive/InternalRxStatementResult.java index 18a640287f..f7e7434e08 100644 --- a/driver/src/main/java/org/neo4j/driver/internal/reactive/InternalRxStatementResult.java +++ b/driver/src/main/java/org/neo4j/driver/internal/reactive/InternalRxStatementResult.java @@ -112,7 +112,7 @@ synchronized CompletionStage initCursorFuture() } @Override - public Publisher summary() + public Publisher consume() { return Mono.create( sink -> getCursorFuture().whenComplete( ( cursor, completionError ) -> { if ( cursor != null ) diff --git a/driver/src/main/java/org/neo4j/driver/internal/util/ErrorUtil.java b/driver/src/main/java/org/neo4j/driver/internal/util/ErrorUtil.java index 3e448040cb..bf157e3f1c 100644 --- a/driver/src/main/java/org/neo4j/driver/internal/util/ErrorUtil.java +++ b/driver/src/main/java/org/neo4j/driver/internal/util/ErrorUtil.java @@ -26,8 +26,9 @@ import org.neo4j.driver.exceptions.AuthenticationException; import org.neo4j.driver.exceptions.ClientException; import org.neo4j.driver.exceptions.DatabaseException; -import org.neo4j.driver.exceptions.Neo4jException; import org.neo4j.driver.exceptions.FatalDiscoveryException; +import org.neo4j.driver.exceptions.Neo4jException; +import org.neo4j.driver.exceptions.ResultConsumedException; import org.neo4j.driver.exceptions.ServiceUnavailableException; import org.neo4j.driver.exceptions.TransientException; @@ -53,6 +54,12 @@ public static ServiceUnavailableException newConnectionTerminatedError() "or due to restarts of the database" ); } + public static ResultConsumedException newResultConsumedError() + { + return new ResultConsumedException( "Cannot access records on this result any more as the result has already been consumed " + + "or the statement runner where the result is created has already been closed." ); + } + public static Neo4jException newNeo4jError( String code, String message ) { String classification = extractClassification( code ); diff --git a/driver/src/main/java/org/neo4j/driver/internal/util/ServerVersion.java b/driver/src/main/java/org/neo4j/driver/internal/util/ServerVersion.java index 381c0f18cd..1cbe3e0f42 100644 --- a/driver/src/main/java/org/neo4j/driver/internal/util/ServerVersion.java +++ b/driver/src/main/java/org/neo4j/driver/internal/util/ServerVersion.java @@ -64,7 +64,7 @@ public static ServerVersion version( Driver driver ) { try ( Session session = driver.session() ) { - String versionString = session.readTransaction( tx -> tx.run( "RETURN 1" ).summary().server().version() ); + String versionString = session.readTransaction( tx -> tx.run( "RETURN 1" ).consume().server().version() ); return version( versionString ); } } diff --git a/driver/src/main/java/org/neo4j/driver/reactive/RxStatementResult.java b/driver/src/main/java/org/neo4j/driver/reactive/RxStatementResult.java index 03dfc4dd63..6f9abda2af 100644 --- a/driver/src/main/java/org/neo4j/driver/reactive/RxStatementResult.java +++ b/driver/src/main/java/org/neo4j/driver/reactive/RxStatementResult.java @@ -24,6 +24,7 @@ import org.neo4j.driver.Record; import org.neo4j.driver.Statement; +import org.neo4j.driver.exceptions.ResultConsumedException; import org.neo4j.driver.summary.ResultSummary; /** @@ -45,9 +46,9 @@ public interface RxStatementResult *

* When this publisher is {@linkplain Publisher#subscribe(Subscriber) subscribed}, the query statement is sent to the server and executed. * This method does not start the record streaming nor publish query execution error. - * To retrieve the execution result, either {@link #records()} or {@link #summary()} can be used. + * To retrieve the execution result, either {@link #records()} or {@link #consume()} can be used. * {@link #records()} starts record streaming and reports query execution error. - * {@link #summary()} skips record streaming and directly reports query execution error. + * {@link #consume()} skips record streaming and directly reports query execution error. *

* Consuming of execution result ensures the resources (such as network connections) used by this result is freed correctly. * Consuming the keys without consuming the execution result will result in resource leak. @@ -55,7 +56,7 @@ public interface RxStatementResult * and subscribed to enforce the result resources created in the {@link RxSession} (and/or {@link RxTransaction}) to be freed correctly. *

* This publisher can be subscribed many times. The keys published stays the same as the keys are buffered. - * If this publisher is subscribed after the publisher of {@link #records()} or {@link #summary()}, + * If this publisher is subscribed after the publisher of {@link #records()} or {@link #consume()}, * then the buffered keys will be returned. * @return a cold publisher of keys. */ @@ -83,8 +84,7 @@ public interface RxStatementResult * This publisher can only be subscribed by one {@link Subscriber} once. *

* If this publisher is subscribed after {@link #keys()}, then the publish of records is carried out after the arrival of keys. - * If this publisher is subscribed after {@link #summary()}, then the publish of records is already cancelled - * and an empty publisher of zero record will be return. + * If this publisher is subscribed after {@link #consume()}, then a {@link ResultConsumedException} will be thrown. * @return a cold unicast publisher of records. */ Publisher records(); @@ -94,7 +94,7 @@ public interface RxStatementResult *

* {@linkplain Publisher#subscribe(Subscriber) Subscribing} the summary publisher results in the execution of the query followed by the result summary returned. * The summary publisher cancels record publishing if not yet subscribed and directly streams back the summary on query execution completion. - * As a result, the invocation of {@link #records()} after this method, would receive an empty publisher. + * As a result, the invocation of {@link #records()} after this method, would receive an {@link ResultConsumedException}. *

* If subscribed after {@link #keys()}, then the result summary will be published after the query execution without streaming any record to client. * If subscribed after {@link #records()}, then the result summary will be published after the query execution and the streaming of records. @@ -104,5 +104,5 @@ public interface RxStatementResult * This method can be subscribed multiple times. When the {@linkplain ResultSummary summary} arrives, it will be buffered locally for all subsequent calls. * @return a cold publisher of result summary which only arrives after all records. */ - Publisher summary(); + Publisher consume(); } diff --git a/driver/src/test/java/org/neo4j/driver/integration/BookmarkIT.java b/driver/src/test/java/org/neo4j/driver/integration/BookmarkIT.java index 733a5a8e31..c4e3e5167a 100644 --- a/driver/src/test/java/org/neo4j/driver/integration/BookmarkIT.java +++ b/driver/src/test/java/org/neo4j/driver/integration/BookmarkIT.java @@ -166,7 +166,7 @@ void bookmarkRemainsAfterSuccessfulSessionRun() Bookmark bookmark = session.lastBookmark(); assertBookmarkContainsSingleValue( bookmark ); - session.run( "RETURN 1" ).summary(); + session.run( "RETURN 1" ).consume(); assertEquals( bookmark, session.lastBookmark() ); } @@ -181,7 +181,7 @@ void bookmarkRemainsAfterFailedSessionRun() Bookmark bookmark = session.lastBookmark(); assertBookmarkContainsSingleValue( bookmark ); - assertThrows( ClientException.class, () -> session.run( "RETURN" ).summary() ); + assertThrows( ClientException.class, () -> session.run( "RETURN" ).consume() ); assertEquals( bookmark, session.lastBookmark() ); } diff --git a/driver/src/test/java/org/neo4j/driver/integration/ConnectionHandlingIT.java b/driver/src/test/java/org/neo4j/driver/integration/ConnectionHandlingIT.java index 448900932b..3564de3a2e 100644 --- a/driver/src/test/java/org/neo4j/driver/integration/ConnectionHandlingIT.java +++ b/driver/src/test/java/org/neo4j/driver/integration/ConnectionHandlingIT.java @@ -116,7 +116,7 @@ void connectionUsedForSessionRunReturnedToThePoolWhenResultConsumed() Connection connection1 = connectionPool.lastAcquiredConnectionSpy; verify( connection1, never() ).release(); - result.summary(); + result.consume(); Connection connection2 = connectionPool.lastAcquiredConnectionSpy; assertSame( connection1, connection2 ); @@ -131,7 +131,7 @@ void connectionUsedForSessionRunReturnedToThePoolWhenResultSummaryObtained() Connection connection1 = connectionPool.lastAcquiredConnectionSpy; verify( connection1, never() ).release(); - ResultSummary summary = result.summary(); + ResultSummary summary = result.consume(); assertEquals( 5, summary.counters().nodesCreated() ); Connection connection2 = connectionPool.lastAcquiredConnectionSpy; @@ -201,7 +201,7 @@ void connectionUsedForSessionRunReturnedToThePoolWhenServerErrorDuringResultFetc Connection connection1 = connectionPool.lastAcquiredConnectionSpy; verify( connection1, never() ).release(); - assertThrows( ClientException.class, result::summary ); + assertThrows( ClientException.class, result::consume ); Connection connection2 = connectionPool.lastAcquiredConnectionSpy; assertSame( connection1, connection2 ); @@ -355,7 +355,7 @@ void resultSummaryShouldReleaseConnectionUsedBySessionRun() throws Throwable Connection connection1 = connectionPool.lastAcquiredConnectionSpy; assertNull( connection1 ); - StepVerifier.create( Mono.from( res.summary() ) ).expectNextCount( 1 ).verifyComplete(); + StepVerifier.create( Mono.from( res.consume() ) ).expectNextCount( 1 ).verifyComplete(); Connection connection2 = connectionPool.lastAcquiredConnectionSpy; assertNotSame( connection1, connection2 ); diff --git a/driver/src/test/java/org/neo4j/driver/integration/ConnectionPoolIT.java b/driver/src/test/java/org/neo4j/driver/integration/ConnectionPoolIT.java index 4a7bac1cfd..5bfae403b8 100644 --- a/driver/src/test/java/org/neo4j/driver/integration/ConnectionPoolIT.java +++ b/driver/src/test/java/org/neo4j/driver/integration/ConnectionPoolIT.java @@ -173,7 +173,7 @@ private static void startAndCloseTransactions( Driver driver, int txCount ) { for ( StatementResult result : results ) { - result.summary(); + result.consume(); } for ( Transaction tx : transactions ) { diff --git a/driver/src/test/java/org/neo4j/driver/integration/CredentialsIT.java b/driver/src/test/java/org/neo4j/driver/integration/CredentialsIT.java index 8a09cddbe9..3684d24132 100644 --- a/driver/src/test/java/org/neo4j/driver/integration/CredentialsIT.java +++ b/driver/src/test/java/org/neo4j/driver/integration/CredentialsIT.java @@ -76,7 +76,7 @@ void shouldBePossibleToChangePassword() throws Exception try ( Driver driver = GraphDatabase.driver( neo4j.uri(), authToken ); Session session = driver.session() ) { - session.run( "RETURN 1" ).summary(); + session.run( "RETURN 1" ).consume(); } // verify old password does not work @@ -87,7 +87,7 @@ void shouldBePossibleToChangePassword() throws Exception try ( Driver driver = GraphDatabase.driver( CredentialsIT.neo4j.uri(), AuthTokens.basic( "neo4j", newPassword ) ); Session session = driver.session() ) { - session.run( "RETURN 2" ).summary(); + session.run( "RETURN 2" ).consume(); } } diff --git a/driver/src/test/java/org/neo4j/driver/integration/ErrorIT.java b/driver/src/test/java/org/neo4j/driver/integration/ErrorIT.java index af32f970a1..a47f6e9d47 100644 --- a/driver/src/test/java/org/neo4j/driver/integration/ErrorIT.java +++ b/driver/src/test/java/org/neo4j/driver/integration/ErrorIT.java @@ -81,7 +81,7 @@ void shouldThrowHelpfulSyntaxError() ClientException e = assertThrows( ClientException.class, () -> { StatementResult result = session.run( "invalid statement" ); - result.summary(); + result.consume(); } ); assertThat( e.getMessage(), startsWith( "Invalid input" ) ); @@ -94,7 +94,7 @@ void shouldNotAllowMoreTxAfterClientException() Transaction tx = session.beginTransaction(); // And Given an error has occurred - try { tx.run( "invalid" ).summary(); } catch ( ClientException e ) {/*empty*/} + try { tx.run( "invalid" ).consume(); } catch ( ClientException e ) {/*empty*/} // Expect ClientException e = assertThrows( ClientException.class, () -> @@ -109,7 +109,7 @@ void shouldNotAllowMoreTxAfterClientException() void shouldAllowNewStatementAfterRecoverableError() { // Given an error has occurred - try { session.run( "invalid" ).summary(); } catch ( ClientException e ) {/*empty*/} + try { session.run( "invalid" ).consume(); } catch ( ClientException e ) {/*empty*/} // When StatementResult cursor = session.run( "RETURN 1" ); @@ -125,7 +125,7 @@ void shouldAllowNewTransactionAfterRecoverableError() // Given an error has occurred in a prior transaction try ( Transaction tx = session.beginTransaction() ) { - tx.run( "invalid" ).summary(); + tx.run( "invalid" ).consume(); } catch ( ClientException e ) {/*empty*/} @@ -241,7 +241,7 @@ void shouldCloseChannelOnInboundFatalFailureMessage() throws InterruptedExceptio @Test void shouldThrowErrorWithNiceStackTrace( TestInfo testInfo ) { - ClientException error = assertThrows( ClientException.class, () -> session.run( "RETURN 10 / 0" ).summary() ); + ClientException error = assertThrows( ClientException.class, () -> session.run( "RETURN 10 / 0" ).consume() ); // thrown error should have this class & method in the stacktrace StackTraceElement[] stackTrace = error.getStackTrace(); @@ -273,7 +273,7 @@ private Throwable testChannelErrorHandling( Consumer messa try { - session.run( "RETURN 1" ).summary(); + session.run( "RETURN 1" ).consume(); fail( "Exception expected" ); } catch ( Throwable error ) diff --git a/driver/src/test/java/org/neo4j/driver/integration/ExplicitTransactionIT.java b/driver/src/test/java/org/neo4j/driver/integration/ExplicitTransactionIT.java index d6533963bc..6c70ce0c21 100644 --- a/driver/src/test/java/org/neo4j/driver/integration/ExplicitTransactionIT.java +++ b/driver/src/test/java/org/neo4j/driver/integration/ExplicitTransactionIT.java @@ -190,7 +190,7 @@ void shouldBePossibleToRunMoreTransactionsAfterOneIsTerminated() await( session.beginTransactionAsync( TransactionConfig.empty() ) .thenCompose( tx -> tx.runAsync( new Statement( "CREATE (:Node {id: 42})" ), true ) - .thenCompose( StatementResultCursor::summaryAsync ) + .thenCompose( StatementResultCursor::consumeAsync ) .thenApply( ignore -> tx ) ).thenCompose( ExplicitTransaction::commitAsync ) ); diff --git a/driver/src/test/java/org/neo4j/driver/integration/ParametersIT.java b/driver/src/test/java/org/neo4j/driver/integration/ParametersIT.java index 61357bf6bf..85be734b78 100644 --- a/driver/src/test/java/org/neo4j/driver/integration/ParametersIT.java +++ b/driver/src/test/java/org/neo4j/driver/integration/ParametersIT.java @@ -495,7 +495,7 @@ private static byte[] randomByteArray( int length ) private static void expectIOExceptionWithMessage( Value value, String message ) { - ServiceUnavailableException e = assertThrows( ServiceUnavailableException.class, () -> session.run( "RETURN {a}", value ).summary() ); + ServiceUnavailableException e = assertThrows( ServiceUnavailableException.class, () -> session.run( "RETURN {a}", value ).consume() ); Throwable cause = e.getCause(); assertThat( cause, instanceOf( IOException.class ) ); assertThat( cause.getMessage(), equalTo( message ) ); diff --git a/driver/src/test/java/org/neo4j/driver/integration/ResultStreamIT.java b/driver/src/test/java/org/neo4j/driver/integration/ResultStreamIT.java index 05b0832cc5..ef100da112 100644 --- a/driver/src/test/java/org/neo4j/driver/integration/ResultStreamIT.java +++ b/driver/src/test/java/org/neo4j/driver/integration/ResultStreamIT.java @@ -126,7 +126,7 @@ void shouldBeAbleToReuseSessionAfterFailure() { // Given StatementResult res1 = session.run( "INVALID" ); - assertThrows( Exception.class, res1::summary ); + assertThrows( Exception.class, res1::consume ); // When StatementResult res2 = session.run( "RETURN 1" ); @@ -144,8 +144,8 @@ void shouldBeAbleToAccessSummaryAfterFailure() ResultSummary summary; // When - assertThrows( Exception.class, res1::summary ); - summary = res1.summary(); + assertThrows( Exception.class, res1::consume ); + summary = res1.consume(); // Then @@ -171,41 +171,7 @@ void shouldBeAbleToAccessSummaryAfterTransactionFailure() StatementResult result = resultRef.get(); assertNotNull( result ); - assertEquals( 0, result.summary().counters().nodesCreated() ); - } - - @Test - void shouldNotBufferRecordsAfterSummary() - { - // Given - StatementResult result = session.run("UNWIND [1,2] AS a RETURN a"); - - // When - ResultSummary summary = result.summary(); - - // Then - assertThat( summary, notNullValue() ); - assertThat( summary.server().address(), equalTo( "localhost:" + session.boltPort() ) ); - assertThat( summary.counters().nodesCreated(), equalTo( 0 ) ); - - assertFalse( result.hasNext() ); - } - - @Test - void shouldDiscardRecordsAfterConsume() - { - // Given - StatementResult result = session.run("UNWIND [1,2] AS a RETURN a"); - - // When - ResultSummary summary = result.summary(); - - // Then - assertThat( summary, notNullValue() ); - assertThat( summary.server().address(), equalTo( "localhost:" + session.boltPort() ) ); - assertThat( summary.counters().nodesCreated(), equalTo( 0 ) ); - - assertThat( result.hasNext(), equalTo( false ) ); + assertEquals( 0, result.consume().counters().nodesCreated() ); } @Test diff --git a/driver/src/test/java/org/neo4j/driver/integration/RoutingDriverBoltKitTest.java b/driver/src/test/java/org/neo4j/driver/integration/RoutingDriverBoltKitTest.java index 851e4b3de9..274360438d 100644 --- a/driver/src/test/java/org/neo4j/driver/integration/RoutingDriverBoltKitTest.java +++ b/driver/src/test/java/org/neo4j/driver/integration/RoutingDriverBoltKitTest.java @@ -265,7 +265,7 @@ void shouldThrowSessionExpiredIfWriteServerDisappears() throws IOException, Inte try ( Driver driver = GraphDatabase.driver( uri, INSECURE_CONFIG ); Session session = driver.session( builder().withDefaultAccessMode( AccessMode.WRITE ).build() ) ) { - assertThrows( SessionExpiredException.class, () -> session.run( "CREATE (n {name:'Bob'})" ).summary() ); + assertThrows( SessionExpiredException.class, () -> session.run( "CREATE (n {name:'Bob'})" ).consume() ); } finally { @@ -289,7 +289,7 @@ void shouldThrowSessionExpiredIfWriteServerDisappearsWhenUsingTransaction() thro Session session = driver.session( builder().withDefaultAccessMode( AccessMode.WRITE ).build() ); Transaction tx = session.beginTransaction() ) { - assertThrows( SessionExpiredException.class, () -> tx.run( "MATCH (n) RETURN n.name" ).summary() ); + assertThrows( SessionExpiredException.class, () -> tx.run( "MATCH (n) RETURN n.name" ).consume() ); } finally { @@ -446,7 +446,7 @@ void shouldHandleLeaderSwitchWhenWriting() throws IOException, InterruptedExcept boolean failed = false; try ( Session session = driver.session( builder().withDefaultAccessMode( AccessMode.WRITE ).build() ) ) { - session.run( "CREATE ()" ).summary(); + session.run( "CREATE ()" ).consume(); } catch ( SessionExpiredException e ) { @@ -500,7 +500,7 @@ void shouldHandleLeaderSwitchWhenWritingInTransaction() throws IOException, Inte boolean failed = false; try ( Session session = driver.session( builder().withDefaultAccessMode( AccessMode.WRITE ).build() ); Transaction tx = session.beginTransaction() ) { - tx.run( "CREATE ()" ).summary(); + tx.run( "CREATE ()" ).consume(); } catch ( SessionExpiredException e ) { @@ -902,7 +902,7 @@ void shouldServeReadsButFailWritesWhenNoWritersAvailable() throws Exception { assertEquals( asList( "Bob", "Alice", "Tina" ), readStrings( "MATCH (n) RETURN n.name", session ) ); - assertThrows( SessionExpiredException.class, () -> session.run( "CREATE (n {name:'Bob'})" ).summary() ); + assertThrows( SessionExpiredException.class, () -> session.run( "CREATE (n {name:'Bob'})" ).consume() ); } finally { @@ -961,11 +961,11 @@ void shouldTreatRoutingTableWithSingleRouterAsValid() throws Exception StatementResult readResult1 = session.run( "MATCH (n) RETURN n.name" ); assertEquals( 3, readResult1.list().size() ); - assertEquals( "127.0.0.1:9003", readResult1.summary().server().address() ); + assertEquals( "127.0.0.1:9003", readResult1.consume().server().address() ); StatementResult readResult2 = session.run( "MATCH (n) RETURN n.name" ); assertEquals( 3, readResult2.list().size() ); - assertEquals( "127.0.0.1:9004", readResult2.summary().server().address() ); + assertEquals( "127.0.0.1:9004", readResult2.consume().server().address() ); } finally { diff --git a/driver/src/test/java/org/neo4j/driver/integration/SessionBoltV3IT.java b/driver/src/test/java/org/neo4j/driver/integration/SessionBoltV3IT.java index a1802cbbee..f3343f6748 100644 --- a/driver/src/test/java/org/neo4j/driver/integration/SessionBoltV3IT.java +++ b/driver/src/test/java/org/neo4j/driver/integration/SessionBoltV3IT.java @@ -118,21 +118,21 @@ void shouldSetTransactionTimeout() { // create a dummy node Session session = driver.session(); - session.run( "CREATE (:Node)" ).summary(); + session.run( "CREATE (:Node)" ).consume(); try ( Session otherSession = driver.driver().session() ) { try ( Transaction otherTx = otherSession.beginTransaction() ) { // lock dummy node but keep the transaction open - otherTx.run( "MATCH (n:Node) SET n.prop = 1" ).summary(); + otherTx.run( "MATCH (n:Node) SET n.prop = 1" ).consume(); assertTimeoutPreemptively( TX_TIMEOUT_TEST_TIMEOUT, () -> { TransactionConfig config = TransactionConfig.builder().withTimeout( ofMillis( 1 ) ).build(); // run a query in an auto-commit transaction with timeout and try to update the locked dummy node TransientException error = assertThrows( TransientException.class, - () -> session.run( "MATCH (n:Node) SET n.prop = 2", config ).summary() ); + () -> session.run( "MATCH (n:Node) SET n.prop = 2", config ).consume() ); assertThat( error.getMessage(), containsString( "terminated" ) ); } ); } @@ -144,14 +144,14 @@ void shouldSetTransactionTimeoutAsync() { // create a dummy node AsyncSession asyncSession = driver.asyncSession(); - await( await( asyncSession.runAsync( "CREATE (:Node)" ) ).summaryAsync() ); + await( await( asyncSession.runAsync( "CREATE (:Node)" ) ).consumeAsync() ); try ( Session otherSession = driver.driver().session() ) { try ( Transaction otherTx = otherSession.beginTransaction() ) { // lock dummy node but keep the transaction open - otherTx.run( "MATCH (n:Node) SET n.prop = 1" ).summary(); + otherTx.run( "MATCH (n:Node) SET n.prop = 1" ).consume(); assertTimeoutPreemptively( TX_TIMEOUT_TEST_TIMEOUT, () -> { TransactionConfig config = TransactionConfig.builder() @@ -160,7 +160,7 @@ void shouldSetTransactionTimeoutAsync() // run a query in an auto-commit transaction with timeout and try to update the locked dummy node CompletionStage resultFuture = asyncSession.runAsync( "MATCH (n:Node) SET n.prop = 2", config ) - .thenCompose( StatementResultCursor::summaryAsync ); + .thenCompose( StatementResultCursor::consumeAsync ); TransientException error = assertThrows( TransientException.class, () -> await( resultFuture ) ); @@ -200,18 +200,18 @@ void shouldUseBookmarksForAutoCommitTransactions() Session session = driver.session(); Bookmark initialBookmark = session.lastBookmark(); - session.run( "CREATE ()" ).summary(); + session.run( "CREATE ()" ).consume(); Bookmark bookmark1 = session.lastBookmark(); assertNotNull( bookmark1 ); assertNotEquals( initialBookmark, bookmark1 ); - session.run( "CREATE ()" ).summary(); + session.run( "CREATE ()" ).consume(); Bookmark bookmark2 = session.lastBookmark(); assertNotNull( bookmark2 ); assertNotEquals( initialBookmark, bookmark2 ); assertNotEquals( bookmark1, bookmark2 ); - session.run( "CREATE ()" ).summary(); + session.run( "CREATE ()" ).consume(); Bookmark bookmark3 = session.lastBookmark(); assertNotNull( bookmark3 ); assertNotEquals( initialBookmark, bookmark3 ); @@ -234,7 +234,7 @@ void shouldUseBookmarksForAutoCommitAndExplicitTransactions() assertNotNull( bookmark1 ); assertNotEquals( initialBookmark, bookmark1 ); - session.run( "CREATE ()" ).summary(); + session.run( "CREATE ()" ).consume(); Bookmark bookmark2 = session.lastBookmark(); assertNotNull( bookmark2 ); assertNotEquals( initialBookmark, bookmark2 ); @@ -263,7 +263,7 @@ void shouldUseBookmarksForAutoCommitTransactionsAndTransactionFunctions() assertNotNull( bookmark1 ); assertNotEquals( initialBookmark, bookmark1 ); - session.run( "CREATE ()" ).summary(); + session.run( "CREATE ()" ).consume(); Bookmark bookmark2 = session.lastBookmark(); assertNotNull( bookmark2 ); assertNotEquals( initialBookmark, bookmark2 ); diff --git a/driver/src/test/java/org/neo4j/driver/integration/SessionIT.java b/driver/src/test/java/org/neo4j/driver/integration/SessionIT.java index 97b0a564f7..6f3a0a38d1 100644 --- a/driver/src/test/java/org/neo4j/driver/integration/SessionIT.java +++ b/driver/src/test/java/org/neo4j/driver/integration/SessionIT.java @@ -53,6 +53,7 @@ import org.neo4j.driver.exceptions.TransientException; import org.neo4j.driver.internal.DriverFactory; import org.neo4j.driver.internal.cluster.RoutingSettings; +import org.neo4j.driver.exceptions.ResultConsumedException; import org.neo4j.driver.internal.retry.RetrySettings; import org.neo4j.driver.internal.util.DisabledOnNeo4jWith; import org.neo4j.driver.internal.util.DriverFactoryWithFixedRetryLogic; @@ -600,13 +601,13 @@ void transactionRunShouldFailOnDeadlocks() throws Exception Transaction tx = session.beginTransaction() ) { // lock first node - updateNodeId( tx, nodeId1, newNodeId1 ).summary(); + updateNodeId( tx, nodeId1, newNodeId1 ).consume(); latch1.await(); latch2.countDown(); // lock second node - updateNodeId( tx, nodeId2, newNodeId1 ).summary(); + updateNodeId( tx, nodeId2, newNodeId1 ).consume(); tx.commit(); } @@ -619,13 +620,13 @@ void transactionRunShouldFailOnDeadlocks() throws Exception Transaction tx = session.beginTransaction() ) { // lock second node - updateNodeId( tx, nodeId2, newNodeId2 ).summary(); + updateNodeId( tx, nodeId2, newNodeId2 ).consume(); latch1.countDown(); latch2.await(); // lock first node - updateNodeId( tx, nodeId1, newNodeId2 ).summary(); + updateNodeId( tx, nodeId1, newNodeId2 ).consume(); tx.commit(); } @@ -666,13 +667,13 @@ void writeTransactionFunctionShouldRetryDeadlocks() throws Exception Transaction tx = session.beginTransaction() ) { // lock first node - updateNodeId( tx, nodeId1, newNodeId1 ).summary(); + updateNodeId( tx, nodeId1, newNodeId1 ).consume(); latch1.await(); latch2.countDown(); // lock second node - updateNodeId( tx, nodeId2, newNodeId1 ).summary(); + updateNodeId( tx, nodeId2, newNodeId1 ).consume(); tx.commit(); } @@ -686,13 +687,13 @@ void writeTransactionFunctionShouldRetryDeadlocks() throws Exception session.writeTransaction( tx -> { // lock second node - updateNodeId( tx, nodeId2, newNodeId2 ).summary(); + updateNodeId( tx, nodeId2, newNodeId2 ).consume(); latch1.countDown(); await( latch2 ); // lock first node - updateNodeId( tx, nodeId1, newNodeId2 ).summary(); + updateNodeId( tx, nodeId1, newNodeId2 ).consume(); createNodeWithId( nodeId3 ); @@ -793,8 +794,7 @@ void shouldNotBePossibleToConsumeResultAfterSessionIsClosed() result = session.run( "UNWIND range(1, 20000) AS x RETURN x" ); } - List ints = result.list( record -> record.get( 0 ).asInt() ); - assertEquals( 0, ints.size() ); + assertThrows( ResultConsumedException.class, () -> result.list( record -> record.get( 0 ).asInt() ) ); } @Test @@ -804,9 +804,9 @@ void shouldPropagateFailureFromSummary() { StatementResult result = session.run( "RETURN Wrong" ); - ClientException e = assertThrows( ClientException.class, result::summary ); + ClientException e = assertThrows( ClientException.class, result::consume ); assertThat( e.code(), containsString( "SyntaxError" ) ); - assertNotNull( result.summary() ); + assertNotNull( result.consume() ); } } @@ -850,7 +850,7 @@ void shouldCloseCleanlyWhenRunErrorConsumed() session.run( "CREATE ()" ); - ClientException e = assertThrows( ClientException.class, () -> session.run( "RETURN 10 / 0" ).summary() ); + ClientException e = assertThrows( ClientException.class, () -> session.run( "RETURN 10 / 0" ).consume() ); assertThat( e.getMessage(), containsString( "/ by zero" ) ); session.run( "CREATE ()" ); @@ -898,7 +898,7 @@ void shouldNotRetryOnConnectionAcquisitionTimeout() } @Test - void shouldNotAllowConsumingRecordsAfterFailureInSessionClose() + void shouldReportFailureInClose() { Session session = neo4j.driver().session(); @@ -906,8 +906,6 @@ void shouldNotAllowConsumingRecordsAfterFailureInSessionClose() ClientException e = assertThrows( ClientException.class, session::close ); assertThat( e, is( arithmeticError() ) ); - - assertFalse( result.hasNext() ); } @Test @@ -920,12 +918,11 @@ void shouldNotAllowAccessingRecordsAfterSummary() { StatementResult result = session.run( query ); - ResultSummary summary = result.summary(); + ResultSummary summary = result.consume(); assertEquals( query, summary.statement().text() ); assertEquals( StatementType.READ_ONLY, summary.statementType() ); - List records = result.list(); - assertEquals( 0, records.size() ); + assertThrows( ResultConsumedException.class, result::list ); } } @@ -941,8 +938,7 @@ void shouldNotAllowAccessingRecordsAfterSessionClosed() result = session.run( query ); } - List records = result.list(); - assertEquals( 0, records.size() ); + assertThrows( ResultConsumedException.class, result::list ); } @Test @@ -979,7 +975,7 @@ void shouldAllowToConsumeRecordsSlowlyAndRetrieveSummary() throws InterruptedExc Thread.sleep( 50 ); } - ResultSummary summary = result.summary(); + ResultSummary summary = result.consume(); assertNotNull( summary ); } } @@ -990,10 +986,10 @@ void shouldBeResponsiveToThreadInterruptWhenWaitingForResult() try ( Session session1 = neo4j.driver().session(); Session session2 = neo4j.driver().session() ) { - session1.run( "CREATE (:Person {name: 'Beta Ray Bill'})" ).summary(); + session1.run( "CREATE (:Person {name: 'Beta Ray Bill'})" ).consume(); Transaction tx = session1.beginTransaction(); - tx.run( "MATCH (n:Person {name: 'Beta Ray Bill'}) SET n.hammer = 'Mjolnir'" ).summary(); + tx.run( "MATCH (n:Person {name: 'Beta Ray Bill'}) SET n.hammer = 'Mjolnir'" ).consume(); // now 'Beta Ray Bill' node is locked @@ -1003,7 +999,7 @@ void shouldBeResponsiveToThreadInterruptWhenWaitingForResult() try { ServiceUnavailableException e = assertThrows( ServiceUnavailableException.class, - () -> session2.run( "MATCH (n:Person {name: 'Beta Ray Bill'}) SET n.hammer = 'Stormbreaker'" ).summary() ); + () -> session2.run( "MATCH (n:Person {name: 'Beta Ray Bill'}) SET n.hammer = 'Stormbreaker'" ).consume() ); assertThat( e.getMessage(), containsString( "Connection to the database terminated" ) ); assertThat( e.getMessage(), containsString( "Thread interrupted" ) ); } @@ -1029,10 +1025,10 @@ void shouldAllowLongRunningQueryWithConnectTimeout() throws Exception Session session1 = driver.session(); Session session2 = driver.session(); - session1.run( "CREATE (:Avenger {name: 'Hulk'})" ).summary(); + session1.run( "CREATE (:Avenger {name: 'Hulk'})" ).consume(); Transaction tx = session1.beginTransaction(); - tx.run( "MATCH (a:Avenger {name: 'Hulk'}) SET a.power = 100 RETURN a" ).summary(); + tx.run( "MATCH (a:Avenger {name: 'Hulk'}) SET a.power = 100 RETURN a" ).consume(); // Hulk node is now locked @@ -1085,7 +1081,7 @@ void shouldAllowConsumingEmptyResult() try ( Session session = neo4j.driver().session() ) { StatementResult result = session.run( "UNWIND [] AS x RETURN x" ); - ResultSummary summary = result.summary(); + ResultSummary summary = result.consume(); assertNotNull( summary ); assertEquals( StatementType.READ_ONLY, summary.statementType() ); } @@ -1102,37 +1098,17 @@ void shouldAllowListEmptyResult() } @Test - void shouldConsume() - { - try ( Session session = neo4j.driver().session() ) - { - String query = "UNWIND [1, 2, 3, 4, 5] AS x RETURN x"; - StatementResult result = session.run( query ); - - ResultSummary summary = result.summary(); - assertEquals( query, summary.statement().text() ); - assertEquals( StatementType.READ_ONLY, summary.statementType() ); - - assertFalse( result.hasNext() ); - assertEquals( emptyList(), result.list() ); - } - } - - @Test - void shouldConsumeWithFailure() + void shouldReportFailureInSummary() { try ( Session session = neo4j.driver().session() ) { String query = "UNWIND [1, 2, 3, 4, 0] AS x RETURN 10 / x"; StatementResult result = session.run( query ); - ClientException e = assertThrows( ClientException.class, result::summary ); + ClientException e = assertThrows( ClientException.class, result::consume ); assertThat( e, is( arithmeticError() ) ); - assertFalse( result.hasNext() ); - assertEquals( emptyList(), result.list() ); - - ResultSummary summary = result.summary(); + ResultSummary summary = result.consume(); assertEquals( query, summary.statement().text() ); } } @@ -1196,8 +1172,8 @@ void shouldSupportNestedQueries() try ( Session session = neo4j.driver().session() ) { // populate db with test data - session.run( "UNWIND range(1, 100) AS x CREATE (:Property {id: x})" ).summary(); - session.run( "UNWIND range(1, 10) AS x CREATE (:Resource {id: x})" ).summary(); + session.run( "UNWIND range(1, 100) AS x CREATE (:Property {id: x})" ).consume(); + session.run( "UNWIND range(1, 10) AS x CREATE (:Resource {id: x})" ).consume(); int seenProperties = 0; int seenResources = 0; @@ -1307,7 +1283,7 @@ void shouldErrorDatabaseWhenDatabaseIsAbsent() throws Throwable ClientException error = assertThrows( ClientException.class, () -> { StatementResult result = session.run( "RETURN 1" ); - result.summary(); + result.consume(); } ); assertThat( error.getMessage(), containsString( "Database does not exist. Database name: 'foo'" ) ); @@ -1325,7 +1301,7 @@ void shouldErrorDatabaseNameUsingTxWhenDatabaseIsAbsent() throws Throwable ClientException error = assertThrows( ClientException.class, () -> { Transaction transaction = session.beginTransaction(); StatementResult result = transaction.run( "RETURN 1" ); - result.summary(); + result.consume(); }); assertThat( error.getMessage(), containsString( "Database does not exist. Database name: 'foo'" ) ); session.close(); @@ -1340,7 +1316,7 @@ void shouldErrorDatabaseNameUsingTxWithRetriesWhenDatabaseIsAbsent() throws Thro // When trying to run the query on a database that does not exist ClientException error = assertThrows( ClientException.class, () -> { - session.readTransaction( tx -> tx.run( "RETURN 1" ).summary() ); + session.readTransaction( tx -> tx.run( "RETURN 1" ).consume() ); }); assertThat( error.getMessage(), containsString( "Database does not exist. Database name: 'foo'" ) ); session.close(); diff --git a/driver/src/test/java/org/neo4j/driver/integration/SessionMixIT.java b/driver/src/test/java/org/neo4j/driver/integration/SessionMixIT.java index cb6dbae22b..70274c759f 100644 --- a/driver/src/test/java/org/neo4j/driver/integration/SessionMixIT.java +++ b/driver/src/test/java/org/neo4j/driver/integration/SessionMixIT.java @@ -109,8 +109,8 @@ void shouldAllowUsingBlockingApiInCommonPoolWhenChaining() // move execution to ForkJoinPool.commonPool() .thenApplyAsync( tx -> { - session.run( "UNWIND [1,1,2] AS x CREATE (:Node {id: x})" ).summary(); - session.run( "CREATE (:Node {id: 42})" ).summary(); + session.run( "UNWIND [1,1,2] AS x CREATE (:Node {id: x})" ).consume(); + session.run( "CREATE (:Node {id: 42})" ).consume(); tx.commitAsync(); return tx; } ); diff --git a/driver/src/test/java/org/neo4j/driver/integration/SessionResetIT.java b/driver/src/test/java/org/neo4j/driver/integration/SessionResetIT.java index 2899c1913c..0cc5eafe47 100644 --- a/driver/src/test/java/org/neo4j/driver/integration/SessionResetIT.java +++ b/driver/src/test/java/org/neo4j/driver/integration/SessionResetIT.java @@ -191,7 +191,7 @@ void shouldNotAllowBeginTxIfResetFailureIsNotConsumed() throws Throwable assertThat( e2.getMessage(), containsString( "Cannot run more statements in this transaction" ) ); // Make sure failure from the terminated long running statement is propagated - Neo4jException e3 = assertThrows( Neo4jException.class, result::summary ); + Neo4jException e3 = assertThrows( Neo4jException.class, result::consume ); assertThat( e3.getMessage(), containsString( "The transaction has been terminated" ) ); } } @@ -227,7 +227,7 @@ void shouldBeAbleToBeginTxAfterResetFailureIsConsumed() throws Throwable awaitActiveQueriesToContain( "CALL test.driver.longRunningStatement" ); session.reset(); - Neo4jException e = assertThrows( Neo4jException.class, procedureResult::summary ); + Neo4jException e = assertThrows( Neo4jException.class, procedureResult::consume ); assertThat( e.getMessage(), containsString( "The transaction has been terminated" ) ); tx1.close(); @@ -264,7 +264,7 @@ void shouldKillLongRunningStatement() throws Throwable // When startTime.set( System.currentTimeMillis() ); - result.summary(); // blocking to run the statement + result.consume(); // blocking to run the statement } } ); @@ -338,13 +338,13 @@ void shouldAllowMoreStatementAfterSessionReset() try ( Session session = neo4j.driver().session() ) { - session.run( "RETURN 1" ).summary(); + session.run( "RETURN 1" ).consume(); // When reset the state of this session session.reset(); // Then can run successfully more statements without any error - session.run( "RETURN 2" ).summary(); + session.run( "RETURN 2" ).consume(); } } @@ -427,7 +427,7 @@ void performUpdate( Driver driver, int nodeId, int newNodeId, usedSessionRef.set( session ); latchToWait.await(); StatementResult result = updateNodeId( session, nodeId, newNodeId ); - result.summary(); + result.consume(); } } } ); @@ -448,7 +448,7 @@ public void performUpdate( Driver driver, int nodeId, int newNodeId, usedSessionRef.set( session ); latchToWait.await(); StatementResult result = updateNodeId( tx, nodeId, newNodeId ); - result.summary(); + result.consume(); } } } ); @@ -474,7 +474,7 @@ public void performUpdate( Driver driver, int nodeId, int newNodeId, { invocationsOfWork.incrementAndGet(); StatementResult result = updateNodeId( tx, nodeId, newNodeId ); - result.summary(); + result.consume(); return null; } ); } @@ -585,7 +585,7 @@ private void testResetOfQueryWaitingForLock( NodeIdUpdater nodeIdUpdater ) throw Future txResult = nodeIdUpdater.update( nodeId, newNodeId1, otherSessionRef, nodeLocked ); StatementResult result = updateNodeId( tx, nodeId, newNodeId2 ); - result.summary(); + result.consume(); nodeLocked.countDown(); // give separate thread some time to block on a lock @@ -721,7 +721,7 @@ private static void runQuery( Session session, String query, boolean autoCommit { if ( autoCommit ) { - session.run( query ).summary(); + session.run( query ).consume(); } else { diff --git a/driver/src/test/java/org/neo4j/driver/integration/StatementRunnerCloseIT.java b/driver/src/test/java/org/neo4j/driver/integration/StatementRunnerCloseIT.java new file mode 100644 index 0000000000..12c1360756 --- /dev/null +++ b/driver/src/test/java/org/neo4j/driver/integration/StatementRunnerCloseIT.java @@ -0,0 +1,219 @@ +/* + * Copyright (c) 2002-2019 "Neo4j," + * Neo4j Sweden AB [http://neo4j.com] + * + * This file is part of Neo4j. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.neo4j.driver.integration; + +import org.junit.jupiter.api.AfterEach; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.extension.RegisterExtension; + +import java.util.List; +import java.util.concurrent.ExecutorService; + +import org.neo4j.driver.Driver; +import org.neo4j.driver.Session; +import org.neo4j.driver.StatementResult; +import org.neo4j.driver.async.AsyncSession; +import org.neo4j.driver.async.StatementResultCursor; +import org.neo4j.driver.exceptions.ResultConsumedException; +import org.neo4j.driver.summary.ResultSummary; +import org.neo4j.driver.util.DatabaseExtension; +import org.neo4j.driver.util.ParallelizableIT; + +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertThrows; +import static org.neo4j.driver.util.TestUtil.await; + +@ParallelizableIT +class StatementRunnerCloseIT +{ + @RegisterExtension + static final DatabaseExtension neo4j = new DatabaseExtension(); + + private Driver driver; + private ExecutorService executor; + + @AfterEach + void tearDown() + { + if ( driver != null ) + { + driver.close(); + } + if ( executor != null ) + { + executor.shutdownNow(); + } + } + + @Test + void shouldErrorToAccessRecordsAfterSummary() + { + // Given + StatementResult result = neo4j.driver().session().run("UNWIND [1,2] AS a RETURN a"); + + // When + result.consume(); + + // Then + assertThrows( ResultConsumedException.class, result::hasNext ); + assertThrows( ResultConsumedException.class, result::next ); + assertThrows( ResultConsumedException.class, result::list ); + assertThrows( ResultConsumedException.class, result::single ); + assertThrows( ResultConsumedException.class, result::peek ); + assertThrows( ResultConsumedException.class, ()-> result.stream().toArray() ); + assertThrows( ResultConsumedException.class, () -> result.forEachRemaining( record -> {} ) ); + assertThrows( ResultConsumedException.class, () -> result.list( record -> record ) ); + } + + @Test + void shouldErrorToAccessRecordsAfterClose() + { + // Given + Session session = neo4j.driver().session(); + StatementResult result = session.run("UNWIND [1,2] AS a RETURN a"); + + // When + session.close(); + + // Then + assertThrows( ResultConsumedException.class, result::hasNext ); + assertThrows( ResultConsumedException.class, result::next ); + assertThrows( ResultConsumedException.class, result::list ); + assertThrows( ResultConsumedException.class, result::single ); + assertThrows( ResultConsumedException.class, result::peek ); + assertThrows( ResultConsumedException.class, ()-> result.stream().toArray() ); + assertThrows( ResultConsumedException.class, () -> result.forEachRemaining( record -> {} ) ); + assertThrows( ResultConsumedException.class, () -> result.list( record -> record ) ); + } + + @Test + void shouldAllowSummaryAndKeysAfterSummary() + { + // Given + StatementResult result = neo4j.driver().session().run("UNWIND [1,2] AS a RETURN a"); + List keys = result.keys(); + + // When + ResultSummary summary = result.consume(); + + // Then + ResultSummary summary1 = result.consume(); + List keys1 = result.keys(); + + assertEquals( summary, summary1 ); + assertEquals( keys, keys1 ); + } + + @Test + void shouldAllowSummaryAndKeysAfterClose() + { + // Given + Session session = neo4j.driver().session(); + StatementResult result = session.run("UNWIND [1,2] AS a RETURN a"); + List keys = result.keys(); + ResultSummary summary = result.consume(); + + // When + session.close(); + + // Then + ResultSummary summary1 = result.consume(); + List keys1 = result.keys(); + + assertEquals( summary, summary1 ); + assertEquals( keys, keys1 ); + } + + @Test + void shouldErrorToAccessRecordsAfterSummaryAsync() + { + // Given + AsyncSession session = neo4j.driver().asyncSession(); + StatementResultCursor result = await( session.runAsync( "UNWIND [1,2] AS a RETURN a" ) ); + + // When + await( result.consumeAsync() ); + + // Then + assertThrows( ResultConsumedException.class, () -> await( result.nextAsync() ) ); + assertThrows( ResultConsumedException.class, () -> await( result.peekAsync() ) ); + assertThrows( ResultConsumedException.class, () -> await( result.singleAsync() ) ); + assertThrows( ResultConsumedException.class, () -> await( result.forEachAsync( record -> {} ) ) ); + assertThrows( ResultConsumedException.class, () -> await( result.listAsync() ) ); + assertThrows( ResultConsumedException.class, () -> await( result.listAsync( record -> record ) ) ); + } + + @Test + void shouldErrorToAccessRecordsAfterCloseAsync() + { + // Given + AsyncSession session = neo4j.driver().asyncSession(); + StatementResultCursor result = await( session.runAsync( "UNWIND [1,2] AS a RETURN a" ) ); + + // When + await( session.closeAsync() ); + + // Then + assertThrows( ResultConsumedException.class, () -> await( result.nextAsync() ) ); + assertThrows( ResultConsumedException.class, () -> await( result.peekAsync() ) ); + assertThrows( ResultConsumedException.class, () -> await( result.singleAsync() ) ); + assertThrows( ResultConsumedException.class, () -> await( result.forEachAsync( record -> {} ) ) ); + assertThrows( ResultConsumedException.class, () -> await( result.listAsync() ) ); + assertThrows( ResultConsumedException.class, () -> await( result.listAsync( record -> record ) ) ); } + + @Test + void shouldAllowSummaryAndKeysAfterSummaryAsync() + { + // Given + AsyncSession session = neo4j.driver().asyncSession(); + StatementResultCursor result = await( session.runAsync( "UNWIND [1,2] AS a RETURN a" ) ); + + List keys = result.keys(); + + // When + ResultSummary summary = await( result.consumeAsync() ); + + // Then + ResultSummary summary1 = await( result.consumeAsync() ); + List keys1 = result.keys(); + + assertEquals( summary, summary1 ); + assertEquals( keys, keys1 ); + } + + @Test + void shouldAllowSummaryAndKeysAfterCloseAsync() + { + // Given + AsyncSession session = neo4j.driver().asyncSession(); + StatementResultCursor result = await( session.runAsync( "UNWIND [1,2] AS a RETURN a" ) ); + List keys = result.keys(); + ResultSummary summary = await( result.consumeAsync() ); + + // When + await( session.closeAsync() ); + + // Then + List keys1 = result.keys(); + ResultSummary summary1 = await( result.consumeAsync() ); + + assertEquals( summary, summary1 ); + assertEquals( keys, keys1 ); + } +} diff --git a/driver/src/test/java/org/neo4j/driver/integration/SummaryIT.java b/driver/src/test/java/org/neo4j/driver/integration/SummaryIT.java index bd62e60beb..c8ec5304a1 100644 --- a/driver/src/test/java/org/neo4j/driver/integration/SummaryIT.java +++ b/driver/src/test/java/org/neo4j/driver/integration/SummaryIT.java @@ -88,16 +88,15 @@ void shouldContainBasicMetadata() assertTrue( result.hasNext() ); // When - ResultSummary summary = result.summary(); + ResultSummary summary = result.consume(); // Then - assertFalse( result.hasNext() ); assertThat( summary.statementType(), equalTo( StatementType.READ_ONLY ) ); assertThat( summary.statement().text(), equalTo( statementText ) ); assertThat( summary.statement().parameters(), equalTo( statementParameters ) ); assertFalse( summary.hasPlan() ); assertFalse( summary.hasProfile() ); - assertThat( summary, equalTo( result.summary() ) ); + assertThat( summary, equalTo( result.consume() ) ); } @@ -105,7 +104,7 @@ void shouldContainBasicMetadata() void shouldContainTimeInformation() { // Given - ResultSummary summary = session.run( "UNWIND range(1,1000) AS n RETURN n AS number" ).summary(); + ResultSummary summary = session.run( "UNWIND range(1,1000) AS n RETURN n AS number" ).consume(); // Then assertThat( summary.resultAvailableAfter( TimeUnit.MILLISECONDS ), greaterThanOrEqualTo( 0L ) ); @@ -115,24 +114,24 @@ void shouldContainTimeInformation() @Test void shouldContainCorrectStatistics() { - assertThat( session.run( "CREATE (n)" ).summary().counters().nodesCreated(), equalTo( 1 ) ); - assertThat( session.run( "MATCH (n) DELETE (n)" ).summary().counters().nodesDeleted(), equalTo( 1 ) ); + assertThat( session.run( "CREATE (n)" ).consume().counters().nodesCreated(), equalTo( 1 ) ); + assertThat( session.run( "MATCH (n) DELETE (n)" ).consume().counters().nodesDeleted(), equalTo( 1 ) ); - assertThat( session.run( "CREATE ()-[:KNOWS]->()" ).summary().counters().relationshipsCreated(), equalTo( 1 ) ); - assertThat( session.run( "MATCH ()-[r:KNOWS]->() DELETE r" ).summary().counters().relationshipsDeleted(), equalTo( 1 ) ); + assertThat( session.run( "CREATE ()-[:KNOWS]->()" ).consume().counters().relationshipsCreated(), equalTo( 1 ) ); + assertThat( session.run( "MATCH ()-[r:KNOWS]->() DELETE r" ).consume().counters().relationshipsDeleted(), equalTo( 1 ) ); - assertThat( session.run( "CREATE (n:ALabel)" ).summary().counters().labelsAdded(), equalTo( 1 ) ); - assertThat( session.run( "CREATE (n {magic: 42})" ).summary().counters().propertiesSet(), equalTo( 1 ) ); - assertTrue( session.run( "CREATE (n {magic: 42})" ).summary().counters().containsUpdates() ); - assertThat( session.run( "MATCH (n:ALabel) REMOVE n:ALabel " ).summary().counters().labelsRemoved(), equalTo( 1 ) ); + assertThat( session.run( "CREATE (n:ALabel)" ).consume().counters().labelsAdded(), equalTo( 1 ) ); + assertThat( session.run( "CREATE (n {magic: 42})" ).consume().counters().propertiesSet(), equalTo( 1 ) ); + assertTrue( session.run( "CREATE (n {magic: 42})" ).consume().counters().containsUpdates() ); + assertThat( session.run( "MATCH (n:ALabel) REMOVE n:ALabel " ).consume().counters().labelsRemoved(), equalTo( 1 ) ); - assertThat( session.run( "CREATE INDEX ON :ALabel(prop)" ).summary().counters().indexesAdded(), equalTo( 1 ) ); - assertThat( session.run( "DROP INDEX ON :ALabel(prop)" ).summary().counters().indexesRemoved(), equalTo( 1 ) ); + assertThat( session.run( "CREATE INDEX ON :ALabel(prop)" ).consume().counters().indexesAdded(), equalTo( 1 ) ); + assertThat( session.run( "DROP INDEX ON :ALabel(prop)" ).consume().counters().indexesRemoved(), equalTo( 1 ) ); assertThat( session.run( "CREATE CONSTRAINT ON (book:Book) ASSERT book.isbn IS UNIQUE" ) - .summary().counters().constraintsAdded(), equalTo( 1 ) ); + .consume().counters().constraintsAdded(), equalTo( 1 ) ); assertThat( session.run( "DROP CONSTRAINT ON (book:Book) ASSERT book.isbn IS UNIQUE" ) - .summary().counters().constraintsRemoved(), equalTo( 1 ) ); + .consume().counters().constraintsRemoved(), equalTo( 1 ) ); } @Test @@ -142,25 +141,25 @@ void shouldGetSystemUpdates() throws Throwable try ( Session session = neo4j.driver().session( forDatabase( "system" ) ) ) { StatementResult result = session.run( "CREATE USER foo SET PASSWORD 'bar'" ); - assertThat( result.summary().counters().containsUpdates(), equalTo( false ) ); - assertThat( result.summary().counters().containsSystemUpdates(), equalTo( true ) ); + assertThat( result.consume().counters().containsUpdates(), equalTo( false ) ); + assertThat( result.consume().counters().containsSystemUpdates(), equalTo( true ) ); } } @Test void shouldContainCorrectStatementType() { - assertThat( session.run("MATCH (n) RETURN 1").summary().statementType(), equalTo( StatementType.READ_ONLY )); - assertThat( session.run("CREATE (n)").summary().statementType(), equalTo( StatementType.WRITE_ONLY )); - assertThat( session.run("CREATE (n) RETURN (n)").summary().statementType(), equalTo( StatementType.READ_WRITE )); - assertThat( session.run("CREATE INDEX ON :User(p)").summary().statementType(), equalTo( StatementType.SCHEMA_WRITE )); + assertThat( session.run("MATCH (n) RETURN 1").consume().statementType(), equalTo( StatementType.READ_ONLY )); + assertThat( session.run("CREATE (n)").consume().statementType(), equalTo( StatementType.WRITE_ONLY )); + assertThat( session.run("CREATE (n) RETURN (n)").consume().statementType(), equalTo( StatementType.READ_WRITE )); + assertThat( session.run("CREATE INDEX ON :User(p)").consume().statementType(), equalTo( StatementType.SCHEMA_WRITE )); } @Test void shouldContainCorrectPlan() { // When - ResultSummary summary = session.run( "EXPLAIN MATCH (n) RETURN 1" ).summary(); + ResultSummary summary = session.run( "EXPLAIN MATCH (n) RETURN 1" ).consume(); // Then assertTrue( summary.hasPlan() ); @@ -176,7 +175,7 @@ void shouldContainCorrectPlan() void shouldContainProfile() { // When - ResultSummary summary = session.run( "PROFILE RETURN 1" ).summary(); + ResultSummary summary = session.run( "PROFILE RETURN 1" ).consume(); // Then assertTrue( summary.hasProfile() ); @@ -193,7 +192,7 @@ void shouldContainProfile() void shouldContainNotifications() { // When - ResultSummary summary = session.run( "EXPLAIN MATCH (n:ThisLabelDoesNotExist) RETURN n" ).summary(); + ResultSummary summary = session.run( "EXPLAIN MATCH (n:ThisLabelDoesNotExist) RETURN n" ).consume(); // Then List notifications = summary.notifications(); @@ -211,7 +210,7 @@ void shouldContainNotifications() void shouldContainNoNotifications() throws Throwable { // When - ResultSummary summary = session.run( "RETURN 1" ).summary(); + ResultSummary summary = session.run( "RETURN 1" ).consume(); // Then assertThat( summary.notifications().size(), equalTo( 0 ) ); diff --git a/driver/src/test/java/org/neo4j/driver/integration/TransactionBoltV3IT.java b/driver/src/test/java/org/neo4j/driver/integration/TransactionBoltV3IT.java index e0136c8c45..cf95b19885 100644 --- a/driver/src/test/java/org/neo4j/driver/integration/TransactionBoltV3IT.java +++ b/driver/src/test/java/org/neo4j/driver/integration/TransactionBoltV3IT.java @@ -70,7 +70,7 @@ void shouldSetTransactionMetadata() try ( Transaction tx = driver.session().beginTransaction( config ) ) { - tx.run( "RETURN 1" ).summary(); + tx.run( "RETURN 1" ).consume(); verifyTransactionMetadata( metadata ); } @@ -89,7 +89,7 @@ void shouldSetTransactionMetadataAsync() CompletionStage txFuture = driver.asyncSession().beginTransactionAsync( config ) .thenCompose( tx -> tx.runAsync( "RETURN 1" ) - .thenCompose( StatementResultCursor::summaryAsync ) + .thenCompose( StatementResultCursor::consumeAsync ) .thenApply( ignore -> tx ) ); AsyncTransaction transaction = await( txFuture ); @@ -108,14 +108,14 @@ void shouldSetTransactionTimeout() { // create a dummy node Session session = driver.session(); - session.run( "CREATE (:Node)" ).summary(); + session.run( "CREATE (:Node)" ).consume(); try ( Session otherSession = driver.driver().session() ) { try ( Transaction otherTx = otherSession.beginTransaction() ) { // lock dummy node but keep the transaction open - otherTx.run( "MATCH (n:Node) SET n.prop = 1" ).summary(); + otherTx.run( "MATCH (n:Node) SET n.prop = 1" ).consume(); assertTimeoutPreemptively( TX_TIMEOUT_TEST_TIMEOUT, () -> { TransactionConfig config = TransactionConfig.builder() @@ -145,14 +145,14 @@ void shouldSetTransactionTimeoutAsync() Session session = driver.session(); AsyncSession asyncSession = driver.asyncSession(); - session.run( "CREATE (:Node)" ).summary(); + session.run( "CREATE (:Node)" ).consume(); try ( Session otherSession = driver.driver().session() ) { try ( Transaction otherTx = otherSession.beginTransaction() ) { // lock dummy node but keep the transaction open - otherTx.run( "MATCH (n:Node) SET n.prop = 1" ).summary(); + otherTx.run( "MATCH (n:Node) SET n.prop = 1" ).consume(); assertTimeoutPreemptively( TX_TIMEOUT_TEST_TIMEOUT, () -> { TransactionConfig config = TransactionConfig.builder() diff --git a/driver/src/test/java/org/neo4j/driver/integration/TransactionIT.java b/driver/src/test/java/org/neo4j/driver/integration/TransactionIT.java index 5c219d93fe..ca4eaa3468 100644 --- a/driver/src/test/java/org/neo4j/driver/integration/TransactionIT.java +++ b/driver/src/test/java/org/neo4j/driver/integration/TransactionIT.java @@ -230,12 +230,12 @@ void shouldHandleFailureAfterClosingTransaction() // GIVEN a successful query in a transaction Transaction tx = session.beginTransaction(); StatementResult result = tx.run( "CREATE (n) RETURN n" ); - result.summary(); + result.consume(); tx.commit(); tx.close(); // WHEN when running a malformed query in the original session - assertThrows( ClientException.class, () -> session.run( "CREAT (n) RETURN n" ).summary() ); + assertThrows( ClientException.class, () -> session.run( "CREAT (n) RETURN n" ).consume() ); } @SuppressWarnings( "ConstantConditions" ) @@ -308,7 +308,7 @@ void shouldRollBackTxIfErrorWithConsume() try ( Transaction tx = session.beginTransaction() ) { StatementResult result = tx.run( "invalid" ); - result.summary(); + result.consume(); } } ); @@ -327,9 +327,9 @@ void shouldPropagateFailureFromSummary() { StatementResult result = tx.run( "RETURN Wrong" ); - ClientException e = assertThrows( ClientException.class, result::summary ); + ClientException e = assertThrows( ClientException.class, result::consume ); assertThat( e.code(), containsString( "SyntaxError" ) ); - assertNotNull( result.summary() ); + assertNotNull( result.consume() ); } } @@ -338,11 +338,11 @@ void shouldBeResponsiveToThreadInterruptWhenWaitingForResult() { try ( Session otherSession = session.driver().session() ) { - session.run( "CREATE (:Person {name: 'Beta Ray Bill'})" ).summary(); + session.run( "CREATE (:Person {name: 'Beta Ray Bill'})" ).consume(); Transaction tx1 = session.beginTransaction(); Transaction tx2 = otherSession.beginTransaction(); - tx1.run( "MATCH (n:Person {name: 'Beta Ray Bill'}) SET n.hammer = 'Mjolnir'" ).summary(); + tx1.run( "MATCH (n:Person {name: 'Beta Ray Bill'}) SET n.hammer = 'Mjolnir'" ).consume(); // now 'Beta Ray Bill' node is locked @@ -352,7 +352,7 @@ void shouldBeResponsiveToThreadInterruptWhenWaitingForResult() try { ServiceUnavailableException e = assertThrows( ServiceUnavailableException.class, - () -> tx2.run( "MATCH (n:Person {name: 'Beta Ray Bill'}) SET n.hammer = 'Stormbreaker'" ).summary() ); + () -> tx2.run( "MATCH (n:Person {name: 'Beta Ray Bill'}) SET n.hammer = 'Stormbreaker'" ).consume() ); assertThat( e.getMessage(), containsString( "Connection to the database terminated" ) ); assertThat( e.getMessage(), containsString( "Thread interrupted while waiting for result to arrive" ) ); } @@ -369,11 +369,11 @@ void shouldBeResponsiveToThreadInterruptWhenWaitingForCommit() { try ( Session otherSession = session.driver().session() ) { - session.run( "CREATE (:Person {name: 'Beta Ray Bill'})" ).summary(); + session.run( "CREATE (:Person {name: 'Beta Ray Bill'})" ).consume(); Transaction tx1 = session.beginTransaction(); Transaction tx2 = otherSession.beginTransaction(); - tx1.run( "MATCH (n:Person {name: 'Beta Ray Bill'}) SET n.hammer = 'Mjolnir'" ).summary(); + tx1.run( "MATCH (n:Person {name: 'Beta Ray Bill'}) SET n.hammer = 'Mjolnir'" ).consume(); // now 'Beta Ray Bill' node is locked @@ -407,7 +407,7 @@ void shouldThrowWhenConnectionKilledDuringTransaction() try ( Session session1 = driver.session(); Transaction tx = session1.beginTransaction() ) { - tx.run( "CREATE (:MyNode {id: 1})" ).summary(); + tx.run( "CREATE (:MyNode {id: 1})" ).consume(); // kill all network channels for ( Channel channel: factory.channels() ) @@ -415,7 +415,7 @@ void shouldThrowWhenConnectionKilledDuringTransaction() channel.close().syncUninterruptibly(); } - tx.run( "CREATE (:MyNode {id: 1})" ).summary(); + tx.run( "CREATE (:MyNode {id: 1})" ).consume(); } } ); @@ -433,7 +433,7 @@ void shouldFailToCommitAfterFailure() throws Throwable List xs = tx.run( "UNWIND [1,2,3] AS x CREATE (:Node) RETURN x" ).list( record -> record.get( 0 ).asInt() ); assertEquals( asList( 1, 2, 3 ), xs ); - ClientException error1 = assertThrows( ClientException.class, () -> tx.run( "RETURN unknown" ).summary() ); + ClientException error1 = assertThrows( ClientException.class, () -> tx.run( "RETURN unknown" ).consume() ); assertThat( error1.code(), containsString( "SyntaxError" ) ); ClientException error2 = assertThrows( ClientException.class, tx::commit ); @@ -449,13 +449,13 @@ void shouldDisallowQueriesAfterFailureWhenResultsAreConsumed() List xs = tx.run( "UNWIND [1,2,3] AS x CREATE (:Node) RETURN x" ).list( record -> record.get( 0 ).asInt() ); assertEquals( asList( 1, 2, 3 ), xs ); - ClientException error1 = assertThrows( ClientException.class, () -> tx.run( "RETURN unknown" ).summary() ); + ClientException error1 = assertThrows( ClientException.class, () -> tx.run( "RETURN unknown" ).consume() ); assertThat( error1.code(), containsString( "SyntaxError" ) ); - ClientException error2 = assertThrows( ClientException.class, () -> tx.run( "CREATE (:OtherNode)" ).summary() ); + ClientException error2 = assertThrows( ClientException.class, () -> tx.run( "CREATE (:OtherNode)" ).consume() ); assertThat( error2.getMessage(), startsWith( "Cannot run more statements in this transaction" ) ); - ClientException error3 = assertThrows( ClientException.class, () -> tx.run( "RETURN 42" ).summary() ); + ClientException error3 = assertThrows( ClientException.class, () -> tx.run( "RETURN 42" ).consume() ); assertThat( error3.getMessage(), startsWith( "Cannot run more statements in this transaction" ) ); } diff --git a/driver/src/test/java/org/neo4j/driver/integration/async/AsyncSessionIT.java b/driver/src/test/java/org/neo4j/driver/integration/async/AsyncSessionIT.java index 5aec4b2cb4..e5b16e57f8 100644 --- a/driver/src/test/java/org/neo4j/driver/integration/async/AsyncSessionIT.java +++ b/driver/src/test/java/org/neo4j/driver/integration/async/AsyncSessionIT.java @@ -49,6 +49,7 @@ import org.neo4j.driver.exceptions.SessionExpiredException; import org.neo4j.driver.exceptions.TransientException; import org.neo4j.driver.internal.InternalBookmark; +import org.neo4j.driver.exceptions.ResultConsumedException; import org.neo4j.driver.internal.util.DisabledOnNeo4jWith; import org.neo4j.driver.internal.util.EnabledOnNeo4jWith; import org.neo4j.driver.internal.util.Futures; @@ -59,7 +60,6 @@ import org.neo4j.driver.util.ParallelizableIT; import static java.util.Collections.emptyIterator; -import static java.util.Collections.emptyList; import static java.util.Collections.emptyMap; import static java.util.concurrent.CompletableFuture.completedFuture; import static org.hamcrest.Matchers.containsString; @@ -285,7 +285,7 @@ void shouldExposeResultSummaryForSimpleQuery() Value params = parameters( "id", 1, "name", "TheNode" ); StatementResultCursor cursor = await( session.runAsync( query, params ) ); - ResultSummary summary = await( cursor.summaryAsync() ); + ResultSummary summary = await( cursor.consumeAsync() ); assertEquals( new Statement( query, params ), summary.statement() ); assertEquals( 1, summary.counters().nodesCreated() ); @@ -307,7 +307,7 @@ void shouldExposeResultSummaryForExplainQuery() String query = "EXPLAIN CREATE (),() WITH * MATCH (n)-->(m) CREATE (n)-[:HI {id: 'id'}]->(m) RETURN n, m"; StatementResultCursor cursor = await( session.runAsync( query ) ); - ResultSummary summary = await( cursor.summaryAsync() ); + ResultSummary summary = await( cursor.consumeAsync() ); assertEquals( new Statement( query ), summary.statement() ); assertEquals( 0, summary.counters().nodesCreated() ); @@ -333,7 +333,7 @@ void shouldExposeResultSummaryForProfileQuery() String query = "PROFILE CREATE (:Node)-[:KNOWS]->(:Node) WITH * MATCH (n) RETURN n"; StatementResultCursor cursor = await( session.runAsync( query ) ); - ResultSummary summary = await( cursor.summaryAsync() ); + ResultSummary summary = await( cursor.consumeAsync() ); assertEquals( new Statement( query ), summary.statement() ); assertEquals( 2, summary.counters().nodesCreated() ); @@ -639,7 +639,7 @@ void shouldBeginTxAfterRunFailureToAcquireConnection() assertThrows( ServiceUnavailableException.class, () -> { StatementResultCursor cursor = await( session.runAsync( "RETURN 42" ) ); - await( cursor.summaryAsync() ); + await( cursor.consumeAsync() ); } ); neo4j.startDb(); @@ -802,7 +802,7 @@ void shouldCloseCleanlyWhenRunErrorConsumed() { StatementResultCursor cursor = await( session.runAsync( "SomeWrongQuery" ) ); - ClientException e = assertThrows( ClientException.class, () -> await( cursor.summaryAsync() ) ); + ClientException e = assertThrows( ClientException.class, () -> await( cursor.consumeAsync() ) ); assertThat( e.getMessage(), startsWith( "Invalid input" ) ); assertNull( await( session.closeAsync() ) ); } @@ -812,31 +812,19 @@ void shouldCloseCleanlyWhenPullAllErrorConsumed() { StatementResultCursor cursor = await( session.runAsync( "UNWIND range(10, 0, -1) AS x RETURN 1 / x" ) ); - ClientException e = assertThrows( ClientException.class, () -> await( cursor.summaryAsync() ) ); + ClientException e = assertThrows( ClientException.class, () -> await( cursor.consumeAsync() ) ); assertThat( e.getMessage(), containsString( "/ by zero" ) ); assertNull( await( session.closeAsync() ) ); } - @Test - void shouldNotBePossibleToConsumeResultAfterSessionIsClosed() - { - CompletionStage cursorStage = session.runAsync( "UNWIND range(1, 20000) AS x RETURN x" ); - - await( session.closeAsync() ); - - StatementResultCursor cursor = await( cursorStage ); - List ints = await( cursor.listAsync( record -> record.get( 0 ).asInt() ) ); - assertEquals( 0, ints.size() ); - } - @Test void shouldPropagateFailureFromSummary() { StatementResultCursor cursor = await( session.runAsync( "RETURN Something" ) ); - ClientException e = assertThrows( ClientException.class, () -> await( cursor.summaryAsync() ) ); + ClientException e = assertThrows( ClientException.class, () -> await( cursor.consumeAsync() ) ); assertThat( e.code(), containsString( "SyntaxError" ) ); - assertNotNull( await( cursor.summaryAsync() ) ); + assertNotNull( await( cursor.consumeAsync() ) ); } @Test @@ -879,45 +867,6 @@ void shouldPropagateFailureFromFirstIllegalQuery() assertEquals( 0, countNodesByLabel( "Node3" ) ); } - @Test - void shouldNotAllowAccessingRecordsAfterSummary() - { - int recordCount = 10_000; - String query = "UNWIND range(1, " + recordCount + ") AS x RETURN 'Hello-' + x"; - - CompletionStage summaryAndRecordsStage = session.runAsync( query ) - .thenCompose( cursor -> cursor.summaryAsync().thenCompose( summary -> cursor.listAsync() - .thenApply( records -> new SummaryAndRecords( summary, records ) ) ) ); - - SummaryAndRecords summaryAndRecords = await( summaryAndRecordsStage ); - ResultSummary summary = summaryAndRecords.summary; - List records = summaryAndRecords.records; - - assertNotNull( summary ); - assertNotNull( records ); - - assertEquals( neo4j.address().toString(), summary.server().address() ); - assertEquals( query, summary.statement().text() ); - assertEquals( StatementType.READ_ONLY, summary.statementType() ); - - assertEquals( 0, records.size() ); - } - - @Test - void shouldNotAllowAccessingRecordsAfterSessionClosed() - { - int recordCount = 7_500; - String query = "UNWIND range(1, " + recordCount + ") AS x RETURN x"; - - CompletionStage> recordsStage = session.runAsync( query ) - .thenCompose( cursor -> session.closeAsync().thenApply( ignore -> cursor ) ) - .thenCompose( StatementResultCursor::listAsync ); - - List records = await( recordsStage ); - - assertEquals( 0, records.size() ); - } - @Test void shouldAllowReturningNullFromAsyncTransactionFunction() { @@ -928,75 +877,6 @@ void shouldAllowReturningNullFromAsyncTransactionFunction() assertNull( await( writeResult ) ); } - @Test - void shouldReturnNoRecordsWhenConsumed() - { - String query = "UNWIND range(1, 5) AS x RETURN x"; - CompletionStage summaryAndRecordStage = session.runAsync( query ) - .thenCompose( cursor -> - { - CompletionStage summaryStage = cursor.summaryAsync(); - CompletionStage recordStage = cursor.nextAsync(); - return summaryStage.thenCombine( recordStage, SummaryAndRecords::new ); - } ); - - SummaryAndRecords result = await( summaryAndRecordStage ); - - assertEquals( query, result.summary.statement().text() ); - assertEquals( StatementType.READ_ONLY, result.summary.statementType() ); - - assertEquals( 1, result.records.size() ); - assertNull( result.records.get( 0 ) ); - } - - @Test - void shouldStopReturningRecordsAfterConsumed() - { - String query = "UNWIND range(1, 5) AS x RETURN x"; - CompletionStage summaryAndRecordsStage = session.runAsync( query ) - .thenCompose( cursor -> cursor.nextAsync() // fetch just a single record - .thenCompose( record1 -> - { - // then summary rest - CompletionStage summaryStage = cursor.summaryAsync(); - // and try to fetch another record - CompletionStage record2Stage = cursor.nextAsync(); - return summaryStage.thenCombine( record2Stage, - ( summary, record2 ) -> new SummaryAndRecords( summary, record1, record2 ) ); - } ) ); - - SummaryAndRecords result = await( summaryAndRecordsStage ); - - assertEquals( query, result.summary.statement().text() ); - assertEquals( StatementType.READ_ONLY, result.summary.statementType() ); - - assertEquals( 2, result.records.size() ); - Record record1 = result.records.get( 0 ); - assertNotNull( record1 ); - assertEquals( 1, record1.get( 0 ).asInt() ); - Record record2 = result.records.get( 1 ); - assertNull( record2 ); - } - - @Test - void shouldReturnEmptyListOfRecordsWhenConsumed() - { - String query = "UNWIND range(1, 5) AS x RETURN x"; - CompletionStage summaryAndRecordsStage = session.runAsync( query ) - .thenCompose( cursor -> - { - CompletionStage summaryStage = cursor.summaryAsync(); - CompletionStage> recordsStage = cursor.listAsync(); - return summaryStage.thenCombine( recordsStage, SummaryAndRecords::new ); - } ); - - SummaryAndRecords result = await( summaryAndRecordsStage ); - - assertEquals( query, result.summary.statement().text() ); - assertEquals( StatementType.READ_ONLY, result.summary.statementType() ); - assertEquals( emptyList(), result.records ); - } - private Future>> runNestedQueries( StatementResultCursor inputCursor ) { CompletableFuture>> resultFuture = new CompletableFuture<>(); @@ -1090,14 +970,14 @@ private void testList( String query, List expectedList ) private void testConsume( String query ) { StatementResultCursor cursor = await( session.runAsync( query ) ); - ResultSummary summary = await( cursor.summaryAsync() ); + ResultSummary summary = await( cursor.consumeAsync() ); assertNotNull( summary ); assertEquals( query, summary.statement().text() ); assertEquals( emptyMap(), summary.statement().parameters().asMap() ); - // no records should be available, they should all be summaryd - assertNull( await( cursor.nextAsync() ) ); + // no records should be available, they should all be consumed + assertThrows( ResultConsumedException.class, () -> await( cursor.nextAsync() ) ); } private static class InvocationTrackingWork implements AsyncTransactionWork> @@ -1186,22 +1066,4 @@ private void processFetchResult( Record record, Throwable error, CompletableFutu } } } - - private static class SummaryAndRecords - { - final ResultSummary summary; - final List records; - - SummaryAndRecords( ResultSummary summary, Record... records ) - { - this.summary = summary; - this.records = Arrays.asList( records ); - } - - SummaryAndRecords( ResultSummary summary, List records ) - { - this.summary = summary; - this.records = records; - } - } } diff --git a/driver/src/test/java/org/neo4j/driver/integration/async/AsyncTransactionIT.java b/driver/src/test/java/org/neo4j/driver/integration/async/AsyncTransactionIT.java index 2881820534..55ed04db74 100644 --- a/driver/src/test/java/org/neo4j/driver/integration/async/AsyncTransactionIT.java +++ b/driver/src/test/java/org/neo4j/driver/integration/async/AsyncTransactionIT.java @@ -43,6 +43,7 @@ import org.neo4j.driver.exceptions.NoSuchRecordException; import org.neo4j.driver.exceptions.ServiceUnavailableException; import org.neo4j.driver.Bookmark; +import org.neo4j.driver.exceptions.ResultConsumedException; import org.neo4j.driver.summary.ResultSummary; import org.neo4j.driver.summary.StatementType; import org.neo4j.driver.types.Node; @@ -220,7 +221,7 @@ void shouldFailToCommitAfterSingleWrongStatement() StatementResultCursor cursor = await( tx.runAsync( "RETURN" ) ); - Exception e = assertThrows( Exception.class, () -> await( cursor.summaryAsync() ) ); + Exception e = assertThrows( Exception.class, () -> await( cursor.consumeAsync() ) ); assertThat( e, is( syntaxError( "Unexpected end of input" ) ) ); assertThrows( ClientException.class, () -> await( tx.commitAsync() ) ); @@ -255,7 +256,7 @@ void shouldFailToCommitAfterCoupleCorrectAndSingleWrongStatement() StatementResultCursor cursor3 = await( tx.runAsync( "RETURN" ) ); - Exception e = assertThrows( Exception.class, () -> await( cursor3.summaryAsync() ) ); + Exception e = assertThrows( Exception.class, () -> await( cursor3.consumeAsync() ) ); assertThat( e, is( syntaxError( "Unexpected end of input" ) ) ); assertThrows( ClientException.class, () -> await( tx.commitAsync() ) ); @@ -278,7 +279,7 @@ void shouldAllowRollbackAfterCoupleCorrectAndSingleWrongStatement() StatementResultCursor cursor3 = await( tx.runAsync( "RETURN" ) ); - Exception e = assertThrows( Exception.class, () -> await( cursor3.summaryAsync() ) ); + Exception e = assertThrows( Exception.class, () -> await( cursor3.consumeAsync() ) ); assertThat( e, is( syntaxError( "Unexpected end of input" ) ) ); assertThat( await( tx.rollbackAsync() ), is( nullValue() ) ); } @@ -379,7 +380,7 @@ void shouldExposeResultSummaryForSimpleQuery() AsyncTransaction tx = await( session.beginTransactionAsync() ); StatementResultCursor cursor = await( tx.runAsync( query, params ) ); - ResultSummary summary = await( cursor.summaryAsync() ); + ResultSummary summary = await( cursor.consumeAsync() ); assertEquals( new Statement( query, params ), summary.statement() ); assertEquals( 2, summary.counters().nodesCreated() ); @@ -402,7 +403,7 @@ void shouldExposeResultSummaryForExplainQuery() AsyncTransaction tx = await( session.beginTransactionAsync() ); StatementResultCursor cursor = await( tx.runAsync( query ) ); - ResultSummary summary = await( cursor.summaryAsync() ); + ResultSummary summary = await( cursor.consumeAsync() ); assertEquals( new Statement( query ), summary.statement() ); assertEquals( 0, summary.counters().nodesCreated() ); @@ -430,7 +431,7 @@ void shouldExposeResultSummaryForProfileQuery() AsyncTransaction tx = await( session.beginTransactionAsync() ); StatementResultCursor cursor = await( tx.runAsync( query, params ) ); - ResultSummary summary = await( cursor.summaryAsync() ); + ResultSummary summary = await( cursor.consumeAsync() ); assertEquals( new Statement( query, params ), summary.statement() ); assertEquals( 1, summary.counters().nodesCreated() ); @@ -773,7 +774,7 @@ void shouldFailToCommitWhenRunFailureIsConsumed() AsyncTransaction tx = await( session.beginTransactionAsync() ); StatementResultCursor cursor = await( tx.runAsync( "RETURN Wrong" ) ); - ClientException e1 = assertThrows( ClientException.class, () -> await( cursor.summaryAsync() ) ); + ClientException e1 = assertThrows( ClientException.class, () -> await( cursor.consumeAsync() ) ); assertThat( e1.code(), containsString( "SyntaxError" ) ); ClientException e2 = assertThrows( ClientException.class, () -> await( tx.commitAsync() ) ); @@ -787,7 +788,7 @@ void shouldFailToCommitWhenPullAllFailureIsConsumed() StatementResultCursor cursor = await( tx.runAsync( "FOREACH (value IN [1,2, 'aaa'] | CREATE (:Person {name: 10 / value}))" ) ); - ClientException e1 = assertThrows( ClientException.class, () -> await( cursor.summaryAsync() ) ); + ClientException e1 = assertThrows( ClientException.class, () -> await( cursor.consumeAsync() ) ); assertThat( e1.code(), containsString( "TypeError" ) ); ClientException e2 = assertThrows( ClientException.class, () -> await( tx.commitAsync() ) ); @@ -800,7 +801,7 @@ void shouldRollbackWhenRunFailureIsConsumed() AsyncTransaction tx = await( session.beginTransactionAsync() ); StatementResultCursor cursor = await( tx.runAsync( "RETURN Wrong" ) ); - ClientException e = assertThrows( ClientException.class, () -> await( cursor.summaryAsync() ) ); + ClientException e = assertThrows( ClientException.class, () -> await( cursor.consumeAsync() ) ); assertThat( e.code(), containsString( "SyntaxError" ) ); assertNull( await( tx.rollbackAsync() ) ); } @@ -811,7 +812,7 @@ void shouldRollbackWhenPullAllFailureIsConsumed() AsyncTransaction tx = await( session.beginTransactionAsync() ); StatementResultCursor cursor = await( tx.runAsync( "UNWIND [1, 0] AS x RETURN 5 / x" ) ); - ClientException e = assertThrows( ClientException.class, () -> await( cursor.summaryAsync() ) ); + ClientException e = assertThrows( ClientException.class, () -> await( cursor.consumeAsync() ) ); assertThat( e.getMessage(), containsString( "/ by zero" ) ); assertNull( await( tx.rollbackAsync() ) ); } @@ -823,9 +824,9 @@ void shouldPropagateFailureFromSummary() StatementResultCursor cursor = await( tx.runAsync( "RETURN Wrong" ) ); - ClientException e = assertThrows( ClientException.class, () -> await( cursor.summaryAsync() ) ); + ClientException e = assertThrows( ClientException.class, () -> await( cursor.consumeAsync() ) ); assertThat( e.code(), containsString( "SyntaxError" ) ); - assertNotNull( await( cursor.summaryAsync() ) ); + assertNotNull( await( cursor.consumeAsync() ) ); } private int countNodes( Object id ) @@ -866,13 +867,13 @@ private void testConsume( String query ) { AsyncTransaction tx = await( session.beginTransactionAsync() ); StatementResultCursor cursor = await( tx.runAsync( query ) ); - ResultSummary summary = await( cursor.summaryAsync() ); + ResultSummary summary = await( cursor.consumeAsync() ); assertNotNull( summary ); assertEquals( query, summary.statement().text() ); assertEquals( emptyMap(), summary.statement().parameters().asMap() ); // no records should be available, they should all be consumed - assertNull( await( cursor.nextAsync() ) ); + assertThrows( ResultConsumedException.class, () -> await( cursor.nextAsync() ) ); } } diff --git a/driver/src/test/java/org/neo4j/driver/integration/reactive/RxNestedQueriesIT.java b/driver/src/test/java/org/neo4j/driver/integration/reactive/RxNestedQueriesIT.java new file mode 100644 index 0000000000..8af9a051ed --- /dev/null +++ b/driver/src/test/java/org/neo4j/driver/integration/reactive/RxNestedQueriesIT.java @@ -0,0 +1,146 @@ +/* + * Copyright (c) 2002-2019 "Neo4j," + * Neo4j Sweden AB [http://neo4j.com] + * + * This file is part of Neo4j. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.neo4j.driver.integration.reactive; + +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.extension.RegisterExtension; +import reactor.core.publisher.Flux; +import reactor.core.publisher.Mono; +import reactor.test.StepVerifier; + +import java.util.Collections; + +import org.neo4j.driver.exceptions.TransactionNestingException; +import org.neo4j.driver.internal.util.EnabledOnNeo4jWith; +import org.neo4j.driver.reactive.RxSession; +import org.neo4j.driver.reactive.RxStatementResult; +import org.neo4j.driver.reactive.RxTransaction; +import org.neo4j.driver.util.DatabaseExtension; +import org.neo4j.driver.util.ParallelizableIT; + +import static org.neo4j.driver.internal.util.Neo4jFeature.BOLT_V4; + +@EnabledOnNeo4jWith( BOLT_V4 ) +@ParallelizableIT +class RxNestedQueriesIT +{ + @RegisterExtension + static final DatabaseExtension neo4j = new DatabaseExtension(); + + @Test + void shouldErrorForNestingQueriesAmongSessionRuns() + { + int size = 12555; + + Flux nodeIds = Flux.usingWhen( + Mono.fromSupplier( () -> neo4j.driver().rxSession() ), + session -> Flux.from( session.run( "UNWIND range(1, $size) AS x RETURN x", Collections.singletonMap( "size", size ) ).records() ) + .limitRate( 20 ).flatMap( record -> { + int x = record.get( "x" ).asInt(); + RxStatementResult innerResult = session.run( "CREATE (n:Node {id: $x}) RETURN n.id", Collections.singletonMap( "x", x ) ); + return innerResult.records(); + } ).map( r -> r.get( 0 ).asInt() ), + RxSession::close ); + + StepVerifier.create( nodeIds ).expectError( TransactionNestingException.class ).verify(); + } + + @Test + void shouldErrorForNestingQueriesAmongTransactionFunctions() + { + int size = 12555; + Flux nodeIds = Flux.usingWhen( + Mono.fromSupplier( () -> neo4j.driver().rxSession() ), + session -> Flux.from( session.readTransaction( tx -> + tx.run( "UNWIND range(1, $size) AS x RETURN x", + Collections.singletonMap( "size", size ) ).records() ) ) + .limitRate( 20 ) + .flatMap( record -> { + int x = record.get( "x" ).asInt(); + return session.writeTransaction( tx -> + tx.run( "CREATE (n:Node {id: $x}) RETURN n.id", + Collections.singletonMap( "x", x ) ).records() ); + } ).map( r -> r.get( 0 ).asInt() ), + RxSession::close ); + + StepVerifier.create( nodeIds ).expectError( TransactionNestingException.class ).verify(); + } + + @Test + void shouldErrorForNestingQueriesAmongSessionRunAndTransactionFunction() + { + int size = 12555; + Flux nodeIds = Flux.usingWhen( + Mono.fromSupplier( () -> neo4j.driver().rxSession() ), + session -> Flux.from( session.run( "UNWIND range(1, $size) AS x RETURN x", + Collections.singletonMap( "size", size ) ).records() ) + .limitRate( 20 ) + .flatMap( record -> { + int x = record.get( "x" ).asInt(); + return session.writeTransaction( tx -> + tx.run( "CREATE (n:Node {id: $x}) RETURN n.id", + Collections.singletonMap( "x", x ) ).records() ); + } ).map( r -> r.get( 0 ).asInt() ), + RxSession::close ); + + StepVerifier.create( nodeIds ).expectError( TransactionNestingException.class ).verify(); + } + + @Test + void shouldErrorForNestingQueriesAmongTransactionFunctionAndSessionRun() + { + int size = 12555; + Flux nodeIds = Flux.usingWhen( + Mono.fromSupplier( () -> neo4j.driver().rxSession() ), + session -> Flux.from( session.readTransaction( tx -> + tx.run( "UNWIND range(1, $size) AS x RETURN x", + Collections.singletonMap( "size", size ) ).records() ) ) + .limitRate( 20 ) + .flatMap( record -> { + int x = record.get( "x" ).asInt(); + return session.run( "CREATE (n:Node {id: $x}) RETURN n.id", + Collections.singletonMap( "x", x ) ).records(); + } ).map( r -> r.get( 0 ).asInt() ), + RxSession::close ); + + StepVerifier.create( nodeIds ).expectError( TransactionNestingException.class ).verify(); + } + + @Test + void shouldHandleNestedQueriesInTheSameTransaction() throws Throwable + { + int size = 12555; + + RxSession session = neo4j.driver().rxSession(); + Flux nodeIds = Flux.usingWhen( + session.beginTransaction(), + tx -> { + RxStatementResult result = tx.run( "UNWIND range(1, $size) AS x RETURN x", + Collections.singletonMap( "size", size ) ); + return Flux.from( result.records() ).limitRate( 20 ).flatMap( record -> { + int x = record.get( "x" ).asInt(); + RxStatementResult innerResult = tx.run( "CREATE (n:Node {id: $x}) RETURN n.id", + Collections.singletonMap( "x", x ) ); + return innerResult.records(); + } ).map( record -> record.get( 0 ).asInt() ); + }, RxTransaction::commit, ( tx, error ) -> tx.rollback(), null ); + + StepVerifier.create( nodeIds ).expectNextCount( size ).verifyComplete(); + } +} diff --git a/driver/src/test/java/org/neo4j/driver/integration/reactive/RxSessionIT.java b/driver/src/test/java/org/neo4j/driver/integration/reactive/RxSessionIT.java index 34db8ede54..4c32733a80 100644 --- a/driver/src/test/java/org/neo4j/driver/integration/reactive/RxSessionIT.java +++ b/driver/src/test/java/org/neo4j/driver/integration/reactive/RxSessionIT.java @@ -38,8 +38,8 @@ import org.neo4j.driver.exceptions.SessionExpiredException; import org.neo4j.driver.exceptions.TransientException; import org.neo4j.driver.internal.util.EnabledOnNeo4jWith; -import org.neo4j.driver.reactive.RxStatementResult; import org.neo4j.driver.reactive.RxSession; +import org.neo4j.driver.reactive.RxStatementResult; import org.neo4j.driver.reactive.RxTransaction; import org.neo4j.driver.reactive.RxTransactionWork; import org.neo4j.driver.util.DatabaseExtension; diff --git a/driver/src/test/java/org/neo4j/driver/integration/reactive/RxStatementResultIT.java b/driver/src/test/java/org/neo4j/driver/integration/reactive/RxStatementResultIT.java index f27a145cb1..7498fd2995 100644 --- a/driver/src/test/java/org/neo4j/driver/integration/reactive/RxStatementResultIT.java +++ b/driver/src/test/java/org/neo4j/driver/integration/reactive/RxStatementResultIT.java @@ -211,7 +211,7 @@ void shouldOnlyErrorRecordAfterFailure() // When Flux keys = Flux.from( result.keys() ); Flux records = Flux.from( result.records() ); - Mono summaryMono = Mono.from( result.summary() ); + Mono summaryMono = Mono.from( result.consume() ); // Then StepVerifier.create( keys ).verifyComplete(); @@ -239,7 +239,7 @@ void shouldErrorOnSummaryIfNoRecord() throws Throwable // When Flux keys = Flux.from( result.keys() ); - Mono summaryMono = Mono.from( result.summary() ); + Mono summaryMono = Mono.from( result.consume() ); // Then StepVerifier.create( keys ).verifyComplete(); @@ -272,7 +272,7 @@ void shouldDiscardRecords() .thenCancel() .verify(); - StepVerifier.create( result.summary() ) // I shall be able to receive summary + StepVerifier.create( result.consume() ) // I shall be able to receive summary .assertNext( summary -> { // Then assertThat( summary, notNullValue() ); @@ -300,7 +300,7 @@ void shouldStreamCorrectRecordsBackBeforeError() private void verifyCanAccessSummary( RxStatementResult res ) { - StepVerifier.create( res.summary() ).assertNext( summary -> { + StepVerifier.create( res.consume() ).assertNext( summary -> { assertThat( summary.statement().text(), equalTo( "UNWIND [1,2,3,4] AS a RETURN a" ) ); assertThat( summary.counters().nodesCreated(), equalTo( 0 ) ); assertThat( summary.statementType(), equalTo( StatementType.READ_ONLY ) ); diff --git a/driver/src/test/java/org/neo4j/driver/integration/reactive/RxTransactionIT.java b/driver/src/test/java/org/neo4j/driver/integration/reactive/RxTransactionIT.java index e8782e205e..e5515ecded 100644 --- a/driver/src/test/java/org/neo4j/driver/integration/reactive/RxTransactionIT.java +++ b/driver/src/test/java/org/neo4j/driver/integration/reactive/RxTransactionIT.java @@ -30,19 +30,18 @@ import java.util.ArrayList; import java.util.Arrays; -import java.util.Collections; import java.util.List; import java.util.Map; import java.util.NoSuchElementException; import java.util.concurrent.atomic.AtomicInteger; import java.util.stream.Stream; +import org.neo4j.driver.Bookmark; import org.neo4j.driver.Record; import org.neo4j.driver.Statement; import org.neo4j.driver.Value; import org.neo4j.driver.exceptions.ClientException; import org.neo4j.driver.exceptions.ServiceUnavailableException; -import org.neo4j.driver.Bookmark; import org.neo4j.driver.internal.util.EnabledOnNeo4jWith; import org.neo4j.driver.reactive.RxSession; import org.neo4j.driver.reactive.RxStatementResult; @@ -124,7 +123,7 @@ void shouldBePossibleToRunSingleStatementAndCommit() Flux ids = Flux.usingWhen( session.beginTransaction(), tx -> Flux.from( tx.run( "CREATE (n:Node {id: 42}) RETURN n" ).records() ) .map( record -> record.get( 0 ).asNode().get( "id" ).asInt() ), - RxTransaction::commit, RxTransaction::rollback ); + RxTransaction::commit, ( tx, error ) -> tx.rollback(), null ); StepVerifier.create( ids ).expectNext( 42 ).verifyComplete(); assertEquals( 1, countNodes( 42 ) ); @@ -349,7 +348,7 @@ void shouldAllowRollbackAfterFailedCommit() { Flux records = Flux.usingWhen( session.beginTransaction(), tx -> Flux.from( tx.run( "WRONG" ).records() ), - RxTransaction::commit, RxTransaction::rollback ); + RxTransaction::commit, ( tx, error ) -> tx.rollback(), null ); StepVerifier.create( records ).verifyErrorSatisfies( error -> assertThat( error.getMessage(), containsString( "Invalid input" ) ) ); @@ -389,7 +388,7 @@ void shouldExposeResultSummaryForSimpleQuery() RxStatementResult result = tx.run( query, params ); await( result.records() ); // we run and stream - ResultSummary summary = await( Mono.from( result.summary() ) ); + ResultSummary summary = await( Mono.from( result.consume() ) ); assertEquals( new Statement( query, params ), summary.statement() ); assertEquals( 2, summary.counters().nodesCreated() ); @@ -417,7 +416,7 @@ void shouldExposeResultSummaryForExplainQuery() RxStatementResult result = tx.run( query ); await( result.records() ); // we run and stream - ResultSummary summary = await( Mono.from( result.summary() ) ); + ResultSummary summary = await( Mono.from( result.consume() ) ); assertEquals( new Statement( query ), summary.statement() ); assertEquals( 0, summary.counters().nodesCreated() ); @@ -449,7 +448,7 @@ void shouldExposeResultSummaryForProfileQuery() RxStatementResult result = tx.run( query, params ); await( result.records() ); // we run and stream - ResultSummary summary = await( Mono.from( result.summary() ) ); + ResultSummary summary = await( Mono.from( result.consume() ) ); assertEquals( new Statement( query, params ), summary.statement() ); assertEquals( 1, summary.counters().nodesCreated() ); @@ -502,7 +501,7 @@ void shouldFailForEachWhenActionFails() Flux records = Flux.usingWhen( session.beginTransaction(), tx -> Flux.from( tx.run( "RETURN 'Hi!'" ).records() ).doOnNext( record -> { throw e; } ), RxTransaction::commit, - RxTransaction::rollback ); + ( tx, error ) -> tx.rollback(), null ); StepVerifier.create( records ).expectErrorSatisfies( error -> assertEquals( e, error ) ).verify(); } @@ -547,7 +546,7 @@ void shouldFailWhenListTransformationFunctionFails() Flux records = Flux.usingWhen( session.beginTransaction(), tx -> Flux.from( tx.run( "RETURN 'Hi!'" ).records() ).map( record -> { throw e; } ), - RxTransaction::commit, RxTransaction::rollback ); + RxTransaction::commit, ( tx, error ) -> tx.rollback(), null ); StepVerifier.create( records ).expectErrorSatisfies( error -> { assertEquals( e, error ); @@ -681,7 +680,7 @@ void shouldUpdateSessionBookmarkAfterCommit() await( Flux.usingWhen( session.beginTransaction(), tx -> tx.run( "CREATE (:MyNode)" ).records(), RxTransaction::commit, - RxTransaction::rollback ) ); + ( tx, error ) -> tx.rollback(), null ) ); Bookmark bookmarkAfter = session.lastBookmark(); @@ -784,30 +783,10 @@ void shouldNotPropagateRunFailureFromSummary() ClientException e = assertThrows( ClientException.class, () -> await( result.records() ) ); assertThat( e.code(), containsString( "SyntaxError" ) ); - await( result.summary() ); + await( result.consume() ); assertCanRollback( tx ); } - @Test - void shouldHandleNestedQueries() throws Throwable - { - int size = 12555; - - Flux nodeIds = Flux.usingWhen( session.beginTransaction(), - tx -> { - RxStatementResult result = tx.run( "UNWIND range(1, $size) AS x RETURN x", Collections.singletonMap( "size", size ) ); - return Flux.from( result.records() ).limitRate( 20 ).flatMap( record -> { - int x = record.get( "x" ).asInt(); - RxStatementResult innerResult = tx.run( "CREATE (n:Node {id: $x}) RETURN n.id", Collections.singletonMap( "x", x ) ); - return innerResult.records(); - } ).map( record -> record.get( 0 ).asInt() ); - }, - RxTransaction::commit, RxTransaction::rollback - ); - - StepVerifier.create( nodeIds ).expectNextCount( size ).verifyComplete(); - } - private int countNodes( Object id ) { RxStatementResult result = session.run( "MATCH (n:Node {id: $id}) RETURN count(n)", parameters( "id", id ) ); @@ -816,20 +795,19 @@ private int countNodes( Object id ) private void testForEach( String query, int expectedSeenRecords ) { - Flux summary = Flux.usingWhen( session.beginTransaction(), tx -> { RxStatementResult result = tx.run( query ); AtomicInteger recordsSeen = new AtomicInteger(); return Flux.from( result.records() ) .doOnNext( record -> recordsSeen.incrementAndGet() ) - .then( Mono.from( result.summary() ) ) + .then( Mono.from( result.consume() ) ) .doOnSuccess( s -> { assertNotNull( s ); assertEquals( query, s.statement().text() ); assertEquals( emptyMap(), s.statement().parameters().asMap() ); assertEquals( expectedSeenRecords, recordsSeen.get() ); } ); - }, RxTransaction::commit, RxTransaction::rollback ); + }, RxTransaction::commit, ( tx, error ) -> tx.rollback(), null ); StepVerifier.create( summary ).expectNextCount( 1 ).verifyComplete(); // we indeed get a summary. } @@ -841,7 +819,7 @@ private void testList( String query, List expectedList ) Flux> records = Flux.usingWhen( session.beginTransaction(), tx -> Flux.from( tx.run( query ).records() ).collectList(), RxTransaction::commit, - RxTransaction::rollback ); + ( tx, error ) -> tx.rollback(), null ); StepVerifier.create( records.single() ).consumeNextWith( allRecords -> { for ( Record record : allRecords ) @@ -856,10 +834,8 @@ private void testList( String query, List expectedList ) private void testConsume( String query ) { Flux summary = Flux.usingWhen( session.beginTransaction(), tx -> - tx.run( query ).summary(), - RxTransaction::commit, - RxTransaction::rollback - ); + tx.run( query ).consume(), + RxTransaction::commit, ( tx, error ) -> tx.rollback(), null ); StepVerifier.create( summary.single() ).consumeNextWith( Assertions::assertNotNull ).verifyComplete(); } diff --git a/driver/src/test/java/org/neo4j/driver/internal/DirectDriverBoltKitTest.java b/driver/src/test/java/org/neo4j/driver/internal/DirectDriverBoltKitTest.java index 9978203924..9ff7cd29a3 100644 --- a/driver/src/test/java/org/neo4j/driver/internal/DirectDriverBoltKitTest.java +++ b/driver/src/test/java/org/neo4j/driver/internal/DirectDriverBoltKitTest.java @@ -22,6 +22,7 @@ import org.junit.jupiter.api.Test; import org.mockito.ArgumentCaptor; import reactor.core.publisher.Flux; +import reactor.core.publisher.Mono; import reactor.test.StepVerifier; import java.net.URI; @@ -355,7 +356,7 @@ void shouldThrowCorrectErrorOnRunFailure() throws Throwable { TransientException error = assertThrows( TransientException.class, () -> { StatementResult result = transaction.run( "RETURN 1" ); - result.summary(); + result.consume(); } ); assertThat( error.code(), equalTo( "Neo.TransientError.General.DatabaseUnavailable" ) ); } @@ -375,7 +376,7 @@ void shouldThrowCorrectErrorOnCommitFailure() throws Throwable { Transaction transaction = session.beginTransaction(); StatementResult result = transaction.run( "CREATE (n {name:'Bob'})" ); - result.summary(); + result.consume(); TransientException error = assertThrows( TransientException.class, transaction::commit ); assertThat( error.code(), equalTo( "Neo.TransientError.General.DatabaseUnavailable" ) ); @@ -395,7 +396,7 @@ void shouldAllowDatabaseNameInSessionRun() throws Throwable Session session = driver.session( builder().withDatabase( "mydatabase" ).withDefaultAccessMode( AccessMode.READ ).build() ) ) { final StatementResult result = session.run( "MATCH (n) RETURN n.name" ); - result.summary(); + result.consume(); } finally { @@ -411,7 +412,7 @@ void shouldAllowDatabaseNameInBeginTransaction() throws Throwable try ( Driver driver = GraphDatabase.driver( "bolt://localhost:9001", INSECURE_CONFIG ); Session session = driver.session( forDatabase( "mydatabase" ) ) ) { - session.readTransaction( tx -> tx.run( "MATCH (n) RETURN n.name" ).summary() ); + session.readTransaction( tx -> tx.run( "MATCH (n) RETURN n.name" ).consume() ); } finally { @@ -426,8 +427,8 @@ void shouldDiscardIfPullNotFinished() throws Throwable try ( Driver driver = GraphDatabase.driver( "bolt://localhost:9001", INSECURE_CONFIG ) ) { - Flux keys = Flux.using( - driver::rxSession, + Flux keys = Flux.usingWhen( + Mono.fromSupplier( driver::rxSession ), session -> session.readTransaction( tx -> tx.run( "UNWIND [1,2,3,4] AS a RETURN a" ).keys() ), RxSession::close ); StepVerifier.create( keys ).expectNext( "a" ).verifyComplete(); diff --git a/driver/src/test/java/org/neo4j/driver/internal/InternalStatementResultTest.java b/driver/src/test/java/org/neo4j/driver/internal/InternalStatementResultTest.java index cd16dd0b30..f8435c6178 100644 --- a/driver/src/test/java/org/neo4j/driver/internal/InternalStatementResultTest.java +++ b/driver/src/test/java/org/neo4j/driver/internal/InternalStatementResultTest.java @@ -29,12 +29,14 @@ import org.neo4j.driver.Statement; import org.neo4j.driver.StatementResult; import org.neo4j.driver.Value; -import org.neo4j.driver.async.StatementResultCursor; import org.neo4j.driver.exceptions.NoSuchRecordException; +import org.neo4j.driver.internal.cursor.AsyncStatementResultCursor; import org.neo4j.driver.internal.cursor.AsyncStatementResultCursorImpl; +import org.neo4j.driver.internal.cursor.DisposableAsyncStatementResultCursor; import org.neo4j.driver.internal.handlers.LegacyPullAllResponseHandler; import org.neo4j.driver.internal.handlers.PullAllResponseHandler; import org.neo4j.driver.internal.handlers.PullResponseCompletionListener; +import org.neo4j.driver.exceptions.ResultConsumedException; import org.neo4j.driver.internal.handlers.RunResponseHandler; import org.neo4j.driver.internal.spi.Connection; import org.neo4j.driver.internal.value.NullValue; @@ -180,10 +182,10 @@ void singleShouldThrowOnEmptyResult() @Test void singleShouldThrowOnConsumedResult() { - assertThrows( NoSuchRecordException.class, () -> + assertThrows( ResultConsumedException.class, () -> { StatementResult result = createResult( 2 ); - result.summary(); + result.consume(); result.single(); } ); } @@ -193,13 +195,13 @@ void shouldConsumeTwice() { // GIVEN StatementResult result = createResult( 2 ); - result.summary(); + result.consume(); // WHEN - result.summary(); + result.consume(); // THEN - assertFalse( result.hasNext() ); + assertThrows( ResultConsumedException.class, result::hasNext ); } @Test @@ -367,8 +369,8 @@ private StatementResult createResult( int numberOfRecords ) } pullAllHandler.onSuccess( emptyMap() ); - StatementResultCursor cursor = new AsyncStatementResultCursorImpl( runHandler, pullAllHandler ); - return new InternalStatementResult( connection, cursor ); + AsyncStatementResultCursor cursor = new AsyncStatementResultCursorImpl( runHandler, pullAllHandler ); + return new InternalStatementResult( connection, new DisposableAsyncStatementResultCursor( cursor ) ); } private List values( Record record ) diff --git a/driver/src/test/java/org/neo4j/driver/internal/InternalTransactionTest.java b/driver/src/test/java/org/neo4j/driver/internal/InternalTransactionTest.java index f75f7bf130..44434ba763 100644 --- a/driver/src/test/java/org/neo4j/driver/internal/InternalTransactionTest.java +++ b/driver/src/test/java/org/neo4j/driver/internal/InternalTransactionTest.java @@ -93,7 +93,7 @@ void shouldFlushOnRun( Function runReturnOne ) thro setupSuccessfulRunAndPull( connection ); StatementResult result = runReturnOne.apply( tx ); - ResultSummary summary = result.summary(); + ResultSummary summary = result.consume(); verifyRunAndPull( connection, summary.statement().text() ); } @@ -131,7 +131,7 @@ void shouldRollback() throws Throwable void shouldRollbackWhenFailedRun() throws Throwable { setupFailingRun( connection, new RuntimeException( "Bang!" ) ); - assertThrows( RuntimeException.class, () -> tx.run( "RETURN 1" ).summary() ); + assertThrows( RuntimeException.class, () -> tx.run( "RETURN 1" ).consume() ); tx.close(); diff --git a/driver/src/test/java/org/neo4j/driver/internal/async/AsyncStatementResultCursorImplTest.java b/driver/src/test/java/org/neo4j/driver/internal/async/AsyncStatementResultCursorImplTest.java index ab68480607..78b141e3a4 100644 --- a/driver/src/test/java/org/neo4j/driver/internal/async/AsyncStatementResultCursorImplTest.java +++ b/driver/src/test/java/org/neo4j/driver/internal/async/AsyncStatementResultCursorImplTest.java @@ -90,11 +90,11 @@ void shouldReturnSummary() new InternalServerInfo( BoltServerAddress.LOCAL_DEFAULT, anyServerVersion() ), DEFAULT_DATABASE_INFO, StatementType.SCHEMA_WRITE, new InternalSummaryCounters( 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 0 ), null, null, emptyList(), 42, 42 ); - when( pullAllHandler.summaryAsync() ).thenReturn( completedFuture( summary ) ); + when( pullAllHandler.consumeAsync() ).thenReturn( completedFuture( summary ) ); AsyncStatementResultCursorImpl cursor = newCursor( pullAllHandler ); - assertEquals( summary, await( cursor.summaryAsync() ) ); + assertEquals( summary, await( cursor.consumeAsync() ) ); } @Test @@ -200,7 +200,7 @@ void shouldForEachAsyncWhenResultContainsMultipleRecords() .thenReturn( completedWithNull() ); ResultSummary summary = mock( ResultSummary.class ); - when( pullAllHandler.summaryAsync() ).thenReturn( completedFuture( summary ) ); + when( pullAllHandler.consumeAsync() ).thenReturn( completedFuture( summary ) ); AsyncStatementResultCursorImpl cursor = newCursor( pullAllHandler ); @@ -221,7 +221,7 @@ void shouldForEachAsyncWhenResultContainsOneRecords() .thenReturn( completedWithNull() ); ResultSummary summary = mock( ResultSummary.class ); - when( pullAllHandler.summaryAsync() ).thenReturn( completedFuture( summary ) ); + when( pullAllHandler.consumeAsync() ).thenReturn( completedFuture( summary ) ); AsyncStatementResultCursorImpl cursor = newCursor( pullAllHandler ); @@ -239,7 +239,7 @@ void shouldForEachAsyncWhenResultContainsNoRecords() when( pullAllHandler.nextAsync() ).thenReturn( completedWithNull() ); ResultSummary summary = mock( ResultSummary.class ); - when( pullAllHandler.summaryAsync() ).thenReturn( completedFuture( summary ) ); + when( pullAllHandler.consumeAsync() ).thenReturn( completedFuture( summary ) ); AsyncStatementResultCursorImpl cursor = newCursor( pullAllHandler ); @@ -292,22 +292,22 @@ void shouldReturnFailureWhenExists() PullAllResponseHandler pullAllHandler = mock( PullAllResponseHandler.class ); ServiceUnavailableException error = new ServiceUnavailableException( "Hi" ); - when( pullAllHandler.failureAsync() ).thenReturn( completedFuture( error ) ); + when( pullAllHandler.pullAllFailureAsync() ).thenReturn( completedFuture( error ) ); AsyncStatementResultCursorImpl cursor = newCursor( pullAllHandler ); - assertEquals( error, await( cursor.failureAsync() ) ); + assertEquals( error, await( cursor.pullAllFailureAsync() ) ); } @Test void shouldReturnNullFailureWhenDoesNotExist() { PullAllResponseHandler pullAllHandler = mock( PullAllResponseHandler.class ); - when( pullAllHandler.failureAsync() ).thenReturn( completedWithNull() ); + when( pullAllHandler.pullAllFailureAsync() ).thenReturn( completedWithNull() ); AsyncStatementResultCursorImpl cursor = newCursor( pullAllHandler ); - assertNull( await( cursor.failureAsync() ) ); + assertNull( await( cursor.pullAllFailureAsync() ) ); } @Test @@ -377,11 +377,11 @@ void shouldConsumeAsync() { PullAllResponseHandler pullAllHandler = mock( PullAllResponseHandler.class ); ResultSummary summary = mock( ResultSummary.class ); - when( pullAllHandler.summaryAsync() ).thenReturn( completedFuture( summary ) ); + when( pullAllHandler.consumeAsync() ).thenReturn( completedFuture( summary ) ); AsyncStatementResultCursorImpl cursor = newCursor( pullAllHandler ); - assertEquals( summary, await( cursor.summaryAsync() ) ); + assertEquals( summary, await( cursor.consumeAsync() ) ); } @Test @@ -389,11 +389,11 @@ void shouldPropagateFailureInConsumeAsync() { PullAllResponseHandler pullAllHandler = mock( PullAllResponseHandler.class ); RuntimeException error = new RuntimeException( "Hi" ); - when( pullAllHandler.summaryAsync() ).thenReturn( failedFuture( error ) ); + when( pullAllHandler.consumeAsync() ).thenReturn( failedFuture( error ) ); AsyncStatementResultCursorImpl cursor = newCursor( pullAllHandler ); - RuntimeException e = assertThrows( RuntimeException.class, () -> await( cursor.summaryAsync() ) ); + RuntimeException e = assertThrows( RuntimeException.class, () -> await( cursor.consumeAsync() ) ); assertEquals( error, e ); } diff --git a/driver/src/test/java/org/neo4j/driver/internal/async/InternalAsyncSessionTest.java b/driver/src/test/java/org/neo4j/driver/internal/async/InternalAsyncSessionTest.java index 1f8b51227f..828ffb9663 100644 --- a/driver/src/test/java/org/neo4j/driver/internal/async/InternalAsyncSessionTest.java +++ b/driver/src/test/java/org/neo4j/driver/internal/async/InternalAsyncSessionTest.java @@ -138,7 +138,7 @@ void shouldFlushOnRun( Function cursorWithError( private static CompletionStage cursorWithFailureFuture( CompletableFuture future ) { AsyncStatementResultCursorImpl cursor = mock( AsyncStatementResultCursorImpl.class ); - when( cursor.consumeAsync() ).thenReturn( future ); + when( cursor.discardAllFailureAsync() ).thenReturn( future ); return completedFuture( cursor ); } } diff --git a/driver/src/test/java/org/neo4j/driver/internal/cursor/AsyncStatementResultCursorOnlyFactoryTest.java b/driver/src/test/java/org/neo4j/driver/internal/cursor/AsyncStatementResultCursorOnlyFactoryTest.java index 32c8292a79..3831b19852 100644 --- a/driver/src/test/java/org/neo4j/driver/internal/cursor/AsyncStatementResultCursorOnlyFactoryTest.java +++ b/driver/src/test/java/org/neo4j/driver/internal/cursor/AsyncStatementResultCursorOnlyFactoryTest.java @@ -174,6 +174,6 @@ private AsyncStatementResultCursorOnlyFactory newResultCursorFactory( Completabl private void verifyRunCompleted( Connection connection, CompletionStage cursorFuture ) { verify( connection ).write( any( Message.class ), any( RunResponseHandler.class ) ); - assertThat( getNow( cursorFuture ), instanceOf( AsyncStatementResultCursorImpl.class ) ); + assertThat( getNow( cursorFuture ), instanceOf( AsyncStatementResultCursor.class ) ); } } diff --git a/driver/src/test/java/org/neo4j/driver/internal/cursor/DisposableAsyncStatementResultCursorTest.java b/driver/src/test/java/org/neo4j/driver/internal/cursor/DisposableAsyncStatementResultCursorTest.java new file mode 100644 index 0000000000..4ca099e3fe --- /dev/null +++ b/driver/src/test/java/org/neo4j/driver/internal/cursor/DisposableAsyncStatementResultCursorTest.java @@ -0,0 +1,94 @@ +/* + * Copyright (c) 2002-2019 "Neo4j," + * Neo4j Sweden AB [http://neo4j.com] + * + * This file is part of Neo4j. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.neo4j.driver.internal.cursor; + +import org.junit.jupiter.api.Test; + +import org.neo4j.driver.internal.util.Futures; + +import static org.junit.jupiter.api.Assertions.assertFalse; +import static org.junit.jupiter.api.Assertions.assertTrue; +import static org.mockito.ArgumentMatchers.any; +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.when; +import static org.neo4j.driver.util.TestUtil.await; + +class DisposableAsyncStatementResultCursorTest +{ + @Test + void summaryShouldDisposeCursor() throws Throwable + { + // Given + DisposableAsyncStatementResultCursor cursor = newCursor(); + + // When + await( cursor.consumeAsync() ); + + // Then + assertTrue( cursor.isDisposed() ); + } + + @Test + void consumeShouldDisposeCursor() throws Throwable + { + // Given + DisposableAsyncStatementResultCursor cursor = newCursor(); + + // When + await( cursor.discardAllFailureAsync() ); + + // Then + assertTrue( cursor.isDisposed() ); + } + + @Test + void shouldNotDisposeCursor() throws Throwable + { + // Given + DisposableAsyncStatementResultCursor cursor = newCursor(); + + // When + cursor.keys(); + await( cursor.peekAsync() ); + await( cursor.nextAsync() ); + await( cursor.singleAsync() ); + await( cursor.forEachAsync( record -> {} ) ); + await( cursor.listAsync() ); + await( cursor.listAsync( record -> record ) ); + await( cursor.pullAllFailureAsync() ); + + // Then + assertFalse( cursor.isDisposed() ); + } + + private static DisposableAsyncStatementResultCursor newCursor() + { + AsyncStatementResultCursor delegate = mock( AsyncStatementResultCursor.class ); + when( delegate.consumeAsync() ).thenReturn( Futures.completedWithNull() ); + when( delegate.discardAllFailureAsync() ).thenReturn( Futures.completedWithNull() ); + when( delegate.peekAsync() ).thenReturn( Futures.completedWithNull() ); + when( delegate.nextAsync() ).thenReturn( Futures.completedWithNull() ); + when( delegate.singleAsync() ).thenReturn( Futures.completedWithNull() ); + when( delegate.forEachAsync( any() ) ).thenReturn( Futures.completedWithNull() ); + when( delegate.listAsync() ).thenReturn( Futures.completedWithNull() ); + when( delegate.listAsync( any() ) ).thenReturn( Futures.completedWithNull() ); + when( delegate.pullAllFailureAsync() ).thenReturn( Futures.completedWithNull() ); + return new DisposableAsyncStatementResultCursor( delegate ); + } +} diff --git a/driver/src/test/java/org/neo4j/driver/internal/cursor/RxStatementResultCursorImplTest.java b/driver/src/test/java/org/neo4j/driver/internal/cursor/RxStatementResultCursorImplTest.java index 7240e5ac8c..2442757ca3 100644 --- a/driver/src/test/java/org/neo4j/driver/internal/cursor/RxStatementResultCursorImplTest.java +++ b/driver/src/test/java/org/neo4j/driver/internal/cursor/RxStatementResultCursorImplTest.java @@ -25,6 +25,7 @@ import java.util.concurrent.CompletableFuture; import java.util.function.BiConsumer; +import org.neo4j.driver.exceptions.ResultConsumedException; import org.neo4j.driver.internal.handlers.RunResponseHandler; import org.neo4j.driver.internal.handlers.pulln.PullResponseHandler; import org.neo4j.driver.internal.reactive.util.ListBasedPullHandler; @@ -186,6 +187,53 @@ void shouldReturnSummaryFuture() throws Throwable assertTrue( cursor.isDone() ); } + @Test + void shouldNotAllowToInstallRecordConsumerAfterSummary() throws Throwable + { + // Given + RunResponseHandler runHandler = newRunResponseHandler(); + PullResponseHandler pullHandler = new ListBasedPullHandler(); + RxStatementResultCursor cursor = new RxStatementResultCursorImpl( runHandler, pullHandler ); + + // When + cursor.summaryAsync(); + + // Then + assertThrows( ResultConsumedException.class, () -> cursor.installRecordConsumer( null ) ); + } + + @Test + void shouldAllowToCallSummaryMultipleTimes() throws Throwable + { + // Given + RunResponseHandler runHandler = newRunResponseHandler(); + PullResponseHandler pullHandler = new ListBasedPullHandler(); + RxStatementResultCursor cursor = new RxStatementResultCursorImpl( runHandler, pullHandler ); + + // When + cursor.summaryAsync(); + + // Then + cursor.summaryAsync(); + cursor.summaryAsync(); + } + + @Test + void shouldOnlyInstallRecordConsumerOnce() throws Throwable + { + // Given + RunResponseHandler runHandler = newRunResponseHandler(); + PullResponseHandler pullHandler = mock( PullResponseHandler.class ); + RxStatementResultCursor cursor = new RxStatementResultCursorImpl( runHandler, pullHandler ); + + // When + cursor.installRecordConsumer( DISCARD_RECORD_CONSUMER ); // any consumer + cursor.installRecordConsumer( DISCARD_RECORD_CONSUMER ); // any consumer + + // Then + verify( pullHandler ).installRecordConsumer( any() ); + } + @Test void shouldCancelIfNotPulled() throws Throwable { diff --git a/driver/src/test/java/org/neo4j/driver/internal/cursor/StatementResultCursorFactoryImplTest.java b/driver/src/test/java/org/neo4j/driver/internal/cursor/StatementResultCursorFactoryImplTest.java index 490906fc5f..cf8a327855 100644 --- a/driver/src/test/java/org/neo4j/driver/internal/cursor/StatementResultCursorFactoryImplTest.java +++ b/driver/src/test/java/org/neo4j/driver/internal/cursor/StatementResultCursorFactoryImplTest.java @@ -222,7 +222,7 @@ private StatementResultCursorFactoryImpl newResultCursorFactory( CompletableFutu private void verifyRunCompleted( Connection connection, CompletionStage cursorFuture ) { verify( connection ).write( any( Message.class ), any( RunResponseHandler.class ) ); - assertThat( getNow( cursorFuture ), instanceOf( AsyncStatementResultCursorImpl.class ) ); + assertThat( getNow( cursorFuture ), instanceOf( AsyncStatementResultCursor.class ) ); } private void verifyRxRunCompleted( Connection connection, CompletionStage cursorFuture ) diff --git a/driver/src/test/java/org/neo4j/driver/internal/handlers/LegacyPullAllResponseHandlerTest.java b/driver/src/test/java/org/neo4j/driver/internal/handlers/LegacyPullAllResponseHandlerTest.java index 9223fe23bf..a9f243e676 100644 --- a/driver/src/test/java/org/neo4j/driver/internal/handlers/LegacyPullAllResponseHandlerTest.java +++ b/driver/src/test/java/org/neo4j/driver/internal/handlers/LegacyPullAllResponseHandlerTest.java @@ -96,7 +96,7 @@ void shouldNotDisableAutoReadWhenSummaryRequested() List keys = asList( "key1", "key2" ); LegacyPullAllResponseHandler handler = newHandler( keys, connection ); - CompletableFuture summaryFuture = handler.summaryAsync().toCompletableFuture(); + CompletableFuture summaryFuture = handler.consumeAsync().toCompletableFuture(); assertFalse( summaryFuture.isDone() ); int recordCount = LegacyPullAllResponseHandler.RECORD_BUFFER_HIGH_WATERMARK + 10; @@ -122,7 +122,7 @@ void shouldNotDisableAutoReadWhenFailureRequested() List keys = asList( "key1", "key2" ); LegacyPullAllResponseHandler handler = newHandler( keys, connection ); - CompletableFuture failureFuture = handler.failureAsync().toCompletableFuture(); + CompletableFuture failureFuture = handler.pullAllFailureAsync().toCompletableFuture(); assertFalse( failureFuture.isDone() ); int recordCount = LegacyPullAllResponseHandler.RECORD_BUFFER_HIGH_WATERMARK + 5; @@ -163,7 +163,7 @@ void shouldEnableAutoReadOnConnectionWhenFailureRequestedButNotAvailable() throw verify( connection, never() ).enableAutoRead(); verify( connection, never() ).disableAutoRead(); - CompletableFuture failureFuture = handler.failureAsync().toCompletableFuture(); + CompletableFuture failureFuture = handler.pullAllFailureAsync().toCompletableFuture(); assertFalse( failureFuture.isDone() ); verify( connection ).enableAutoRead(); @@ -203,7 +203,7 @@ void shouldReturnEmptyListInListAsyncAfterFailure() handler.onFailure( error ); // consume the error - assertEquals( error, await( handler.failureAsync() ) ); + assertEquals( error, await( handler.pullAllFailureAsync() ) ); assertEquals( emptyList(), await( handler.listAsync( Function.identity() ) ) ); } @@ -220,7 +220,7 @@ void shouldEnableAutoReadOnConnectionWhenSummaryRequestedButNotAvailable() throw verify( connection, never() ).enableAutoRead(); verify( connection, never() ).disableAutoRead(); - CompletableFuture summaryFuture = handler.summaryAsync().toCompletableFuture(); + CompletableFuture summaryFuture = handler.consumeAsync().toCompletableFuture(); assertFalse( summaryFuture.isDone() ); verify( connection ).enableAutoRead(); diff --git a/driver/src/test/java/org/neo4j/driver/internal/handlers/PullAllResponseHandlerTestBase.java b/driver/src/test/java/org/neo4j/driver/internal/handlers/PullAllResponseHandlerTestBase.java index 78dfbdba80..15ea22f33f 100644 --- a/driver/src/test/java/org/neo4j/driver/internal/handlers/PullAllResponseHandlerTestBase.java +++ b/driver/src/test/java/org/neo4j/driver/internal/handlers/PullAllResponseHandlerTestBase.java @@ -64,7 +64,7 @@ void shouldReturnNoFailureWhenAlreadySucceeded() PullAllResponseHandler handler = newHandler(); handler.onSuccess( emptyMap() ); - Throwable failure = await( handler.failureAsync() ); + Throwable failure = await( handler.pullAllFailureAsync() ); assertNull( failure ); } @@ -74,7 +74,7 @@ void shouldReturnNoFailureWhenSucceededAfterFailureRequested() { PullAllResponseHandler handler = newHandler(); - CompletableFuture failureFuture = handler.failureAsync().toCompletableFuture(); + CompletableFuture failureFuture = handler.pullAllFailureAsync().toCompletableFuture(); assertFalse( failureFuture.isDone() ); handler.onSuccess( emptyMap() ); @@ -91,7 +91,7 @@ void shouldReturnFailureWhenAlreadyFailed() RuntimeException failure = new RuntimeException( "Ops" ); handler.onFailure( failure ); - Throwable receivedFailure = await( handler.failureAsync() ); + Throwable receivedFailure = await( handler.pullAllFailureAsync() ); assertEquals( failure, receivedFailure ); } @@ -100,7 +100,7 @@ void shouldReturnFailureWhenFailedAfterFailureRequested() { PullAllResponseHandler handler = newHandler(); - CompletableFuture failureFuture = handler.failureAsync().toCompletableFuture(); + CompletableFuture failureFuture = handler.pullAllFailureAsync().toCompletableFuture(); assertFalse( failureFuture.isDone() ); IOException failure = new IOException( "Broken pipe" ); @@ -115,8 +115,8 @@ void shouldReturnFailureWhenRequestedMultipleTimes() { PullAllResponseHandler handler = newHandler(); - CompletableFuture failureFuture1 = handler.failureAsync().toCompletableFuture(); - CompletableFuture failureFuture2 = handler.failureAsync().toCompletableFuture(); + CompletableFuture failureFuture1 = handler.pullAllFailureAsync().toCompletableFuture(); + CompletableFuture failureFuture2 = handler.pullAllFailureAsync().toCompletableFuture(); assertFalse( failureFuture1.isDone() ); assertFalse( failureFuture2.isDone() ); @@ -139,8 +139,8 @@ void shouldReturnFailureOnlyOnceWhenFailedBeforeFailureRequested() ServiceUnavailableException failure = new ServiceUnavailableException( "Connection terminated" ); handler.onFailure( failure ); - assertEquals( failure, await( handler.failureAsync() ) ); - assertNull( await( handler.failureAsync() ) ); + assertEquals( failure, await( handler.pullAllFailureAsync() ) ); + assertNull( await( handler.pullAllFailureAsync() ) ); } @Test @@ -148,13 +148,13 @@ void shouldReturnFailureOnlyOnceWhenFailedAfterFailureRequested() { PullAllResponseHandler handler = newHandler(); - CompletionStage failureFuture = handler.failureAsync(); + CompletionStage failureFuture = handler.pullAllFailureAsync(); SessionExpiredException failure = new SessionExpiredException( "Network unreachable" ); handler.onFailure( failure ); assertEquals( failure, await( failureFuture ) ); - assertNull( await( handler.failureAsync() ) ); + assertNull( await( handler.pullAllFailureAsync() ) ); } @Test @@ -166,9 +166,9 @@ void shouldReturnSummaryWhenAlreadyFailedAndFailureConsumed() ServiceUnavailableException failure = new ServiceUnavailableException( "Neo4j unreachable" ); handler.onFailure( failure ); - assertEquals( failure, await( handler.failureAsync() ) ); + assertEquals( failure, await( handler.pullAllFailureAsync() ) ); - ResultSummary summary = await( handler.summaryAsync() ); + ResultSummary summary = await( handler.consumeAsync() ); assertNotNull( summary ); assertEquals( statement, summary.statement() ); } @@ -180,7 +180,7 @@ void shouldReturnSummaryWhenAlreadySucceeded() PullAllResponseHandler handler = newHandler( statement ); handler.onSuccess( singletonMap( "type", value( "rw" ) ) ); - ResultSummary summary = await( handler.summaryAsync() ); + ResultSummary summary = await( handler.consumeAsync() ); assertEquals( statement, summary.statement() ); assertEquals( StatementType.READ_WRITE, summary.statementType() ); @@ -192,7 +192,7 @@ void shouldReturnSummaryWhenSucceededAfterSummaryRequested() Statement statement = new Statement( "RETURN 'Hi!" ); PullAllResponseHandler handler = newHandler( statement ); - CompletableFuture summaryFuture = handler.summaryAsync().toCompletableFuture(); + CompletableFuture summaryFuture = handler.consumeAsync().toCompletableFuture(); assertFalse( summaryFuture.isDone() ); handler.onSuccess( singletonMap( "type", value( "r" ) ) ); @@ -212,7 +212,7 @@ void shouldReturnFailureWhenSummaryRequestedWhenAlreadyFailed() RuntimeException failure = new RuntimeException( "Computer is burning" ); handler.onFailure( failure ); - RuntimeException e = assertThrows( RuntimeException.class, () -> await( handler.summaryAsync() ) ); + RuntimeException e = assertThrows( RuntimeException.class, () -> await( handler.consumeAsync() ) ); assertEquals( failure, e ); } @@ -221,7 +221,7 @@ void shouldReturnFailureWhenFailedAfterSummaryRequested() { PullAllResponseHandler handler = newHandler(); - CompletableFuture summaryFuture = handler.summaryAsync().toCompletableFuture(); + CompletableFuture summaryFuture = handler.consumeAsync().toCompletableFuture(); assertFalse( summaryFuture.isDone() ); IOException failure = new IOException( "FAILED to write" ); @@ -237,8 +237,8 @@ void shouldFailSummaryWhenRequestedMultipleTimes() { PullAllResponseHandler handler = newHandler(); - CompletableFuture summaryFuture1 = handler.summaryAsync().toCompletableFuture(); - CompletableFuture summaryFuture2 = handler.summaryAsync().toCompletableFuture(); + CompletableFuture summaryFuture1 = handler.consumeAsync().toCompletableFuture(); + CompletableFuture summaryFuture2 = handler.consumeAsync().toCompletableFuture(); assertFalse( summaryFuture1.isDone() ); assertFalse( summaryFuture2.isDone() ); @@ -264,10 +264,10 @@ void shouldPropagateFailureOnlyOnceFromSummary() IllegalStateException failure = new IllegalStateException( "Some state is illegal :(" ); handler.onFailure( failure ); - RuntimeException e = assertThrows( RuntimeException.class, () -> await( handler.summaryAsync() ) ); + RuntimeException e = assertThrows( RuntimeException.class, () -> await( handler.consumeAsync() ) ); assertEquals( failure, e ); - ResultSummary summary = await( handler.summaryAsync() ); + ResultSummary summary = await( handler.consumeAsync() ); assertNotNull( summary ); assertEquals( statement, summary.statement() ); } diff --git a/driver/src/test/java/org/neo4j/driver/internal/reactive/InternalRxStatementResultTest.java b/driver/src/test/java/org/neo4j/driver/internal/reactive/InternalRxStatementResultTest.java index 630138d29f..77724978b8 100644 --- a/driver/src/test/java/org/neo4j/driver/internal/reactive/InternalRxStatementResultTest.java +++ b/driver/src/test/java/org/neo4j/driver/internal/reactive/InternalRxStatementResultTest.java @@ -149,7 +149,7 @@ void shouldObtainRecordsAndSummary() .expectNext( record2 ) .expectNext( record3 ) .verifyComplete(); - StepVerifier.create( Mono.from( rxResult.summary() ) ).expectNextCount( 1 ).verifyComplete(); + StepVerifier.create( Mono.from( rxResult.consume() ) ).expectNextCount( 1 ).verifyComplete(); } @Test @@ -167,7 +167,7 @@ void shouldCancelStreamingButObtainSummary() StepVerifier.create( Flux.from( rxResult.records() ).limitRate( 1 ).take( 1 ) ) .expectNext( record1 ) .verifyComplete(); - StepVerifier.create( Mono.from( rxResult.summary() ) ).expectNextCount( 1 ).verifyComplete(); + StepVerifier.create( Mono.from( rxResult.consume() ) ).expectNextCount( 1 ).verifyComplete(); } @Test @@ -179,7 +179,7 @@ void shouldErrorIfFailedToCreateCursor() // When & Then StepVerifier.create( Flux.from( rxResult.records() ) ).expectErrorMatches( isEqual( error ) ).verify(); - StepVerifier.create( Mono.from( rxResult.summary() ) ).expectErrorMatches( isEqual( error ) ).verify(); + StepVerifier.create( Mono.from( rxResult.consume() ) ).expectErrorMatches( isEqual( error ) ).verify(); } @Test @@ -191,7 +191,7 @@ void shouldErrorIfFailedToStream() // When & Then StepVerifier.create( Flux.from( rxResult.records() ) ).expectErrorMatches( isEqual( error ) ).verify(); - StepVerifier.create( Mono.from( rxResult.summary() ) ).assertNext( summary -> { + StepVerifier.create( Mono.from( rxResult.consume() ) ).assertNext( summary -> { assertThat( summary, instanceOf( ResultSummary.class ) ); } ).verifyComplete(); } diff --git a/driver/src/test/java/org/neo4j/driver/stress/AbstractStressTestBase.java b/driver/src/test/java/org/neo4j/driver/stress/AbstractStressTestBase.java index 1661b7fad4..9ccc0ae7ba 100644 --- a/driver/src/test/java/org/neo4j/driver/stress/AbstractStressTestBase.java +++ b/driver/src/test/java/org/neo4j/driver/stress/AbstractStressTestBase.java @@ -638,7 +638,7 @@ private Publisher createNodesInTxRx( RxTransaction tx, int batchIndex, int { return Flux.concat( Flux.range( 0, batchSize ).map( index -> batchIndex * batchSize + index ).map( nodeIndex -> { Statement statement = createNodeInTxStatement( nodeIndex ); - return Flux.from( tx.run( statement ).summary() ).then(); // As long as there is no error + return Flux.from( tx.run( statement ).consume() ).then(); // As long as there is no error } ) ); } @@ -682,7 +682,7 @@ private static Void createNodesInTx( Transaction tx, int batchIndex, int batchSi private static void createNodeInTx( Transaction tx, int nodeIndex ) { Statement statement = createNodeInTxStatement( nodeIndex ); - tx.run( statement ).summary(); + tx.run( statement ).consume(); } private static CompletionStage createNodesInTxAsync( AsyncTransaction tx, int batchIndex, int batchSize ) @@ -702,7 +702,7 @@ private static CompletableFuture createNodeInTxAsync( AsyncTransaction tx, { Statement statement = createNodeInTxStatement( nodeIndex ); return tx.runAsync( statement ) - .thenCompose( StatementResultCursor::summaryAsync ) + .thenCompose( StatementResultCursor::consumeAsync ) .thenApply( ignore -> (Void) null ) .toCompletableFuture(); } diff --git a/driver/src/test/java/org/neo4j/driver/stress/AsyncReadQuery.java b/driver/src/test/java/org/neo4j/driver/stress/AsyncReadQuery.java index d4a27136ab..841b115528 100644 --- a/driver/src/test/java/org/neo4j/driver/stress/AsyncReadQuery.java +++ b/driver/src/test/java/org/neo4j/driver/stress/AsyncReadQuery.java @@ -65,6 +65,6 @@ private CompletionStage processAndGetSummary( Record record, Stat Node node = record.get( 0 ).asNode(); assertNotNull( node ); } - return cursor.summaryAsync(); + return cursor.consumeAsync(); } } diff --git a/driver/src/test/java/org/neo4j/driver/stress/AsyncReadQueryInTx.java b/driver/src/test/java/org/neo4j/driver/stress/AsyncReadQueryInTx.java index 62e1fbacf3..feca545ceb 100644 --- a/driver/src/test/java/org/neo4j/driver/stress/AsyncReadQueryInTx.java +++ b/driver/src/test/java/org/neo4j/driver/stress/AsyncReadQueryInTx.java @@ -61,7 +61,7 @@ private CompletionStage processRecordAndGetSummary( Record record Node node = record.get( 0 ).asNode(); assertNotNull( node ); } - return cursor.summaryAsync(); + return cursor.consumeAsync(); } private CompletionStage processSummaryAndCommit( ResultSummary summary, AsyncTransaction tx, C context ) diff --git a/driver/src/test/java/org/neo4j/driver/stress/AsyncWriteQuery.java b/driver/src/test/java/org/neo4j/driver/stress/AsyncWriteQuery.java index 5408baee09..1c7df2a6a6 100644 --- a/driver/src/test/java/org/neo4j/driver/stress/AsyncWriteQuery.java +++ b/driver/src/test/java/org/neo4j/driver/stress/AsyncWriteQuery.java @@ -44,7 +44,7 @@ public CompletionStage execute( C context ) AsyncSession session = newSession( AccessMode.WRITE, context ); return session.runAsync( "CREATE ()" ) - .thenCompose( StatementResultCursor::summaryAsync ) + .thenCompose( StatementResultCursor::consumeAsync ) .handle( ( summary, error ) -> { session.closeAsync(); diff --git a/driver/src/test/java/org/neo4j/driver/stress/AsyncWriteQueryInTx.java b/driver/src/test/java/org/neo4j/driver/stress/AsyncWriteQueryInTx.java index 1cc459d022..7252c68a7b 100644 --- a/driver/src/test/java/org/neo4j/driver/stress/AsyncWriteQueryInTx.java +++ b/driver/src/test/java/org/neo4j/driver/stress/AsyncWriteQueryInTx.java @@ -45,7 +45,7 @@ public CompletionStage execute( C context ) CompletionStage txCommitted = session.beginTransactionAsync().thenCompose( tx -> tx.runAsync( "CREATE ()" ).thenCompose( cursor -> - cursor.summaryAsync().thenCompose( summary -> + cursor.consumeAsync().thenCompose( summary -> tx.commitAsync().thenApply( ignore -> summary ) ) ) ); return txCommitted.handle( ( summary, error ) -> diff --git a/driver/src/test/java/org/neo4j/driver/stress/BlockingFailingQuery.java b/driver/src/test/java/org/neo4j/driver/stress/BlockingFailingQuery.java index 897987fbb4..7864464de4 100644 --- a/driver/src/test/java/org/neo4j/driver/stress/BlockingFailingQuery.java +++ b/driver/src/test/java/org/neo4j/driver/stress/BlockingFailingQuery.java @@ -41,7 +41,7 @@ public void execute( C context ) try ( Session session = newSession( AccessMode.READ, context ) ) { StatementResult result = session.run( "UNWIND [10, 5, 0] AS x RETURN 10 / x" ); - Exception e = assertThrows( Exception.class, result::summary ); + Exception e = assertThrows( Exception.class, result::consume ); assertThat( e, is( arithmeticError() ) ); } } diff --git a/driver/src/test/java/org/neo4j/driver/stress/BlockingFailingQueryInTx.java b/driver/src/test/java/org/neo4j/driver/stress/BlockingFailingQueryInTx.java index 5560242f81..2b8b7e55ec 100644 --- a/driver/src/test/java/org/neo4j/driver/stress/BlockingFailingQueryInTx.java +++ b/driver/src/test/java/org/neo4j/driver/stress/BlockingFailingQueryInTx.java @@ -45,7 +45,7 @@ public void execute( C context ) { StatementResult result = tx.run( "UNWIND [10, 5, 0] AS x RETURN 10 / x" ); - Exception e = assertThrows( Exception.class, result::summary ); + Exception e = assertThrows( Exception.class, result::consume ); assertThat( e, is( arithmeticError() ) ); } } diff --git a/driver/src/test/java/org/neo4j/driver/stress/BlockingReadQuery.java b/driver/src/test/java/org/neo4j/driver/stress/BlockingReadQuery.java index 61dcca3eda..98ee8286e8 100644 --- a/driver/src/test/java/org/neo4j/driver/stress/BlockingReadQuery.java +++ b/driver/src/test/java/org/neo4j/driver/stress/BlockingReadQuery.java @@ -51,7 +51,7 @@ public void execute( C context ) assertNotNull( node ); } - context.readCompleted( result.summary() ); + context.readCompleted( result.consume() ); } } } diff --git a/driver/src/test/java/org/neo4j/driver/stress/BlockingReadQueryInTx.java b/driver/src/test/java/org/neo4j/driver/stress/BlockingReadQueryInTx.java index ac2f0467a6..c12924bd79 100644 --- a/driver/src/test/java/org/neo4j/driver/stress/BlockingReadQueryInTx.java +++ b/driver/src/test/java/org/neo4j/driver/stress/BlockingReadQueryInTx.java @@ -53,7 +53,7 @@ public void execute( C context ) assertNotNull( node ); } - context.readCompleted( result.summary() ); + context.readCompleted( result.consume() ); tx.commit(); } } diff --git a/driver/src/test/java/org/neo4j/driver/stress/BlockingWriteQuery.java b/driver/src/test/java/org/neo4j/driver/stress/BlockingWriteQuery.java index 21857c088d..6293da5a91 100644 --- a/driver/src/test/java/org/neo4j/driver/stress/BlockingWriteQuery.java +++ b/driver/src/test/java/org/neo4j/driver/stress/BlockingWriteQuery.java @@ -56,7 +56,7 @@ public void execute( C context ) if ( queryError == null && result != null ) { - assertEquals( 1, result.summary().counters().nodesCreated() ); + assertEquals( 1, result.consume().counters().nodesCreated() ); context.nodeCreated(); } } diff --git a/driver/src/test/java/org/neo4j/driver/stress/BlockingWriteQueryInTx.java b/driver/src/test/java/org/neo4j/driver/stress/BlockingWriteQueryInTx.java index 55aebee212..16a2847b7a 100644 --- a/driver/src/test/java/org/neo4j/driver/stress/BlockingWriteQueryInTx.java +++ b/driver/src/test/java/org/neo4j/driver/stress/BlockingWriteQueryInTx.java @@ -63,7 +63,7 @@ public void execute( C context ) if ( txError == null && result != null ) { - assertEquals( 1, result.summary().counters().nodesCreated() ); + assertEquals( 1, result.consume().counters().nodesCreated() ); context.nodeCreated(); } } diff --git a/driver/src/test/java/org/neo4j/driver/stress/BlockingWriteQueryUsingReadSession.java b/driver/src/test/java/org/neo4j/driver/stress/BlockingWriteQueryUsingReadSession.java index 088e694bd4..69381865be 100644 --- a/driver/src/test/java/org/neo4j/driver/stress/BlockingWriteQueryUsingReadSession.java +++ b/driver/src/test/java/org/neo4j/driver/stress/BlockingWriteQueryUsingReadSession.java @@ -50,6 +50,6 @@ public void execute( C context ) } } ); assertNotNull( resultRef.get() ); - assertEquals( 0, resultRef.get().summary().counters().nodesCreated() ); + assertEquals( 0, resultRef.get().consume().counters().nodesCreated() ); } } diff --git a/driver/src/test/java/org/neo4j/driver/stress/BlockingWriteQueryUsingReadSessionInTx.java b/driver/src/test/java/org/neo4j/driver/stress/BlockingWriteQueryUsingReadSessionInTx.java index 76e3617ef3..fcd2a28591 100644 --- a/driver/src/test/java/org/neo4j/driver/stress/BlockingWriteQueryUsingReadSessionInTx.java +++ b/driver/src/test/java/org/neo4j/driver/stress/BlockingWriteQueryUsingReadSessionInTx.java @@ -53,6 +53,6 @@ public void execute( C context ) } } ); assertNotNull( resultRef.get() ); - assertEquals( 0, resultRef.get().summary().counters().nodesCreated() ); + assertEquals( 0, resultRef.get().consume().counters().nodesCreated() ); } } diff --git a/driver/src/test/java/org/neo4j/driver/stress/BlockingWrongQuery.java b/driver/src/test/java/org/neo4j/driver/stress/BlockingWrongQuery.java index 3294e882f2..5431662b5f 100644 --- a/driver/src/test/java/org/neo4j/driver/stress/BlockingWrongQuery.java +++ b/driver/src/test/java/org/neo4j/driver/stress/BlockingWrongQuery.java @@ -39,7 +39,7 @@ public void execute( C context ) { try ( Session session = newSession( AccessMode.READ, context ) ) { - Exception e = assertThrows( Exception.class, () -> session.run( "RETURN" ).summary() ); + Exception e = assertThrows( Exception.class, () -> session.run( "RETURN" ).consume() ); assertThat( e, is( syntaxError( "Unexpected end of input" ) ) ); } } diff --git a/driver/src/test/java/org/neo4j/driver/stress/BlockingWrongQueryInTx.java b/driver/src/test/java/org/neo4j/driver/stress/BlockingWrongQueryInTx.java index d18001f64e..f5438b1769 100644 --- a/driver/src/test/java/org/neo4j/driver/stress/BlockingWrongQueryInTx.java +++ b/driver/src/test/java/org/neo4j/driver/stress/BlockingWrongQueryInTx.java @@ -42,7 +42,7 @@ public void execute( C context ) { try ( Transaction tx = beginTransaction( session, context ) ) { - Exception e = assertThrows( Exception.class, () -> tx.run( "RETURN" ).summary() ); + Exception e = assertThrows( Exception.class, () -> tx.run( "RETURN" ).consume() ); assertThat( e, is( syntaxError( "Unexpected end of input" ) ) ); } } diff --git a/driver/src/test/java/org/neo4j/driver/stress/CausalClusteringIT.java b/driver/src/test/java/org/neo4j/driver/stress/CausalClusteringIT.java index ba38a42f82..068764516b 100644 --- a/driver/src/test/java/org/neo4j/driver/stress/CausalClusteringIT.java +++ b/driver/src/test/java/org/neo4j/driver/stress/CausalClusteringIT.java @@ -388,7 +388,7 @@ void shouldNotServeWritesWhenMajorityOfCoresAreDead() { try ( Session session = driver.session( builder().withDefaultAccessMode( AccessMode.WRITE ).build() ) ) { - session.run( "CREATE (p:Person {name: 'Gamora'})" ).summary(); + session.run( "CREATE (p:Person {name: 'Gamora'})" ).consume(); } } ); } @@ -430,7 +430,7 @@ void shouldServeReadsWhenMajorityOfCoresAreDead() { try ( Session session = driver.session( builder().withDefaultAccessMode( AccessMode.WRITE ).build() ) ) { - session.run( "CREATE (p:Person {name: 'Gamora'})" ).summary(); + session.run( "CREATE (p:Person {name: 'Gamora'})" ).consume(); } } ); @@ -569,11 +569,11 @@ RoutingSettings.DEFAULT, RetrySettings.DEFAULT, configWithoutLogging() ) ) { Session session1 = driver.session(); Transaction tx1 = session1.beginTransaction(); - tx1.run( "CREATE (n:Node1 {name: 'Node1'})" ).summary(); + tx1.run( "CREATE (n:Node1 {name: 'Node1'})" ).consume(); Session session2 = driver.session(); Transaction tx2 = session2.beginTransaction(); - tx2.run( "CREATE (n:Node2 {name: 'Node2'})" ).summary(); + tx2.run( "CREATE (n:Node2 {name: 'Node2'})" ).consume(); ServiceUnavailableException error = new ServiceUnavailableException( "Connection broke!" ); driverFactory.setNextRunFailure( error ); @@ -628,7 +628,7 @@ RoutingSettings.DEFAULT, RetrySettings.DEFAULT, configWithoutLogging() ) ) try ( Session session = driver.session( builder().withDefaultAccessMode( AccessMode.WRITE ).build() ) ) { SessionExpiredException e = assertThrows( SessionExpiredException.class, - () -> runCreateNode( session, "Person", "name", "Vision" ).summary() ); + () -> runCreateNode( session, "Person", "name", "Vision" ).consume() ); assertEquals( "Disconnected", e.getCause().getMessage() ); } @@ -708,7 +708,7 @@ void shouldKeepOperatingWhenConnectionsBreak() throws Exception private static void assertUnableToRunMoreStatementsInTx( Transaction tx, ServiceUnavailableException cause ) { - SessionExpiredException e = assertThrows( SessionExpiredException.class, () -> tx.run( "CREATE (n:Node3 {name: 'Node3'})" ).summary() ); + SessionExpiredException e = assertThrows( SessionExpiredException.class, () -> tx.run( "CREATE (n:Node3 {name: 'Node3'})" ).consume() ); assertEquals( cause, e.getCause() ); } @@ -722,7 +722,7 @@ private CompletionStage> combineCursors( StatementResultC private CompletionStage buildRecordAndSummary( StatementResultCursor cursor ) { return cursor.singleAsync().thenCompose( record -> - cursor.summaryAsync().thenApply( summary -> new RecordAndSummary( record, summary ) ) ); + cursor.consumeAsync().thenApply( summary -> new RecordAndSummary( record, summary ) ) ); } private int executeWriteAndReadThroughBolt( ClusterMember member ) throws TimeoutException, InterruptedException @@ -755,7 +755,7 @@ private Function executeWriteAndRead() { return session -> { - session.run( "MERGE (n:Person {name: 'Jim'})" ).summary(); + session.run( "MERGE (n:Person {name: 'Jim'})" ).consume(); Record record = session.run( "MATCH (n:Person) RETURN COUNT(*) AS count" ).next(); return record.get( "count" ).asInt(); }; diff --git a/driver/src/test/java/org/neo4j/driver/stress/RxFailingQuery.java b/driver/src/test/java/org/neo4j/driver/stress/RxFailingQuery.java index 9c61b20a4e..dd04220c2b 100644 --- a/driver/src/test/java/org/neo4j/driver/stress/RxFailingQuery.java +++ b/driver/src/test/java/org/neo4j/driver/stress/RxFailingQuery.java @@ -19,6 +19,7 @@ package org.neo4j.driver.stress; import reactor.core.publisher.Flux; +import reactor.core.publisher.Mono; import java.util.concurrent.CompletableFuture; import java.util.concurrent.CompletionStage; @@ -45,7 +46,7 @@ public RxFailingQuery( Driver driver ) public CompletionStage execute( C context ) { CompletableFuture queryFinished = new CompletableFuture<>(); - Flux.using( () -> newSession( AccessMode.READ, context ), + Flux.usingWhen( Mono.fromSupplier( () -> newSession( AccessMode.READ, context ) ), session -> session.run( "UNWIND [10, 5, 0] AS x RETURN 10 / x" ).records(), RxSession::close ) .subscribe( record -> { diff --git a/driver/src/test/java/org/neo4j/driver/stress/RxFailingQueryInTx.java b/driver/src/test/java/org/neo4j/driver/stress/RxFailingQueryInTx.java index 3b493d0e23..9381a8cd0e 100644 --- a/driver/src/test/java/org/neo4j/driver/stress/RxFailingQueryInTx.java +++ b/driver/src/test/java/org/neo4j/driver/stress/RxFailingQueryInTx.java @@ -49,7 +49,7 @@ public CompletionStage execute( C context ) RxSession session = newSession( AccessMode.READ, context ); Flux.usingWhen( session.beginTransaction(), tx -> tx.run( "UNWIND [10, 5, 0] AS x RETURN 10 / x" ).records(), - RxTransaction::commit, RxTransaction::rollback ) + RxTransaction::commit, ( tx, error ) -> tx.rollback(), null ) .subscribe( record -> { assertThat( record.get( 0 ).asInt(), either( equalTo( 1 ) ).or( equalTo( 2 ) ) ); queryFinished.complete( null ); diff --git a/driver/src/test/java/org/neo4j/driver/stress/RxFailingQueryWithRetries.java b/driver/src/test/java/org/neo4j/driver/stress/RxFailingQueryWithRetries.java index 88dee19732..d668d4ddea 100644 --- a/driver/src/test/java/org/neo4j/driver/stress/RxFailingQueryWithRetries.java +++ b/driver/src/test/java/org/neo4j/driver/stress/RxFailingQueryWithRetries.java @@ -19,6 +19,7 @@ package org.neo4j.driver.stress; import reactor.core.publisher.Flux; +import reactor.core.publisher.Mono; import java.util.concurrent.CompletableFuture; import java.util.concurrent.CompletionStage; @@ -45,7 +46,7 @@ public RxFailingQueryWithRetries( Driver driver ) public CompletionStage execute( C context ) { CompletableFuture queryFinished = new CompletableFuture<>(); - Flux.using( () -> newSession( AccessMode.READ, context ), + Flux.usingWhen( Mono.fromSupplier( () -> newSession( AccessMode.READ, context ) ), session -> session.readTransaction( tx -> tx.run( "UNWIND [10, 5, 0] AS x RETURN 10 / x" ).records() ), RxSession::close ) .subscribe( record -> { diff --git a/driver/src/test/java/org/neo4j/driver/stress/RxReadQuery.java b/driver/src/test/java/org/neo4j/driver/stress/RxReadQuery.java index 14e443ab3b..3878ee7abd 100644 --- a/driver/src/test/java/org/neo4j/driver/stress/RxReadQuery.java +++ b/driver/src/test/java/org/neo4j/driver/stress/RxReadQuery.java @@ -43,7 +43,7 @@ public RxReadQuery( Driver driver, boolean useBookmark ) public CompletionStage execute( C context ) { CompletableFuture queryFinished = new CompletableFuture<>(); - Flux.using( () -> newSession( AccessMode.READ, context ), this::processAndGetSummary, RxSession::close ) + Flux.usingWhen( Mono.fromSupplier( () -> newSession( AccessMode.READ, context ) ), this::processAndGetSummary, RxSession::close ) .subscribe( summary -> { context.readCompleted( summary ); queryFinished.complete( null ); @@ -58,7 +58,7 @@ private Publisher processAndGetSummary( RxSession session ) { RxStatementResult result = session.run( "MATCH (n) RETURN n LIMIT 1" ); Mono records = Flux.from( result.records() ).singleOrEmpty().map( record -> record.get( 0 ).asNode() ); - Mono summaryMono = Mono.from( result.summary() ).single(); + Mono summaryMono = Mono.from( result.consume() ).single(); return records.then( summaryMono ); } } diff --git a/driver/src/test/java/org/neo4j/driver/stress/RxReadQueryInTx.java b/driver/src/test/java/org/neo4j/driver/stress/RxReadQueryInTx.java index c4e9f6cb30..83ba935341 100644 --- a/driver/src/test/java/org/neo4j/driver/stress/RxReadQueryInTx.java +++ b/driver/src/test/java/org/neo4j/driver/stress/RxReadQueryInTx.java @@ -45,7 +45,8 @@ public CompletionStage execute( C context ) { CompletableFuture queryFinished = new CompletableFuture<>(); RxSession session = newSession( AccessMode.READ, context ); - Flux.usingWhen( session.beginTransaction(), this::processAndGetSummary, RxTransaction::commit, RxTransaction::rollback ) + Flux.usingWhen( session.beginTransaction(), this::processAndGetSummary, + RxTransaction::commit, ( tx, error ) -> tx.rollback(), null ) .subscribe( summary -> { context.readCompleted( summary ); queryFinished.complete( null ); @@ -60,7 +61,7 @@ private Publisher processAndGetSummary( RxTransaction tx ) { RxStatementResult result = tx.run( "MATCH (n) RETURN n LIMIT 1" ); Mono records = Flux.from( result.records() ).singleOrEmpty().map( record -> record.get( 0 ).asNode() ); - Mono summaryMono = Mono.from( result.summary() ).single(); + Mono summaryMono = Mono.from( result.consume() ).single(); return records.then( summaryMono ); } } diff --git a/driver/src/test/java/org/neo4j/driver/stress/RxReadQueryWithRetries.java b/driver/src/test/java/org/neo4j/driver/stress/RxReadQueryWithRetries.java index 3781dfe08a..9339571bab 100644 --- a/driver/src/test/java/org/neo4j/driver/stress/RxReadQueryWithRetries.java +++ b/driver/src/test/java/org/neo4j/driver/stress/RxReadQueryWithRetries.java @@ -43,7 +43,7 @@ public RxReadQueryWithRetries( Driver driver, boolean useBookmark ) public CompletionStage execute( C context ) { CompletableFuture queryFinished = new CompletableFuture<>(); - Flux.using( () -> newSession( AccessMode.READ, context ), this::processAndGetSummary, RxSession::close ) + Flux.usingWhen( Mono.fromSupplier( () -> newSession( AccessMode.READ, context ) ), this::processAndGetSummary, RxSession::close ) .subscribe( summary -> { queryFinished.complete( null ); context.readCompleted( summary ); @@ -59,7 +59,7 @@ private Publisher processAndGetSummary( RxSession session ) return session.readTransaction( tx -> { RxStatementResult result = tx.run( "MATCH (n) RETURN n LIMIT 1" ); Mono records = Flux.from( result.records() ).singleOrEmpty().map( record -> record.get( 0 ).asNode() ); - Mono summaryMono = Mono.from( result.summary() ).single(); + Mono summaryMono = Mono.from( result.consume() ).single(); return records.then( summaryMono ); } ); } diff --git a/driver/src/test/java/org/neo4j/driver/stress/RxWriteQuery.java b/driver/src/test/java/org/neo4j/driver/stress/RxWriteQuery.java index bda658dac5..1e131362f7 100644 --- a/driver/src/test/java/org/neo4j/driver/stress/RxWriteQuery.java +++ b/driver/src/test/java/org/neo4j/driver/stress/RxWriteQuery.java @@ -19,6 +19,7 @@ package org.neo4j.driver.stress; import reactor.core.publisher.Flux; +import reactor.core.publisher.Mono; import java.util.concurrent.CompletableFuture; import java.util.concurrent.CompletionStage; @@ -44,8 +45,8 @@ public RxWriteQuery( AbstractStressTestBase stressTest, Driver driver, boolea public CompletionStage execute( C context ) { CompletableFuture queryFinished = new CompletableFuture<>(); - Flux.using( () -> newSession( AccessMode.WRITE, context ), - session -> session.run( "CREATE ()" ).summary(), RxSession::close ) + Flux.usingWhen( Mono.fromSupplier( () -> newSession( AccessMode.WRITE, context ) ), + session -> session.run( "CREATE ()" ).consume(), RxSession::close ) .subscribe( summary -> { queryFinished.complete( null ); assertEquals( 1, summary.counters().nodesCreated() ); diff --git a/driver/src/test/java/org/neo4j/driver/stress/RxWriteQueryInTx.java b/driver/src/test/java/org/neo4j/driver/stress/RxWriteQueryInTx.java index 5b930df03f..673d37d3f8 100644 --- a/driver/src/test/java/org/neo4j/driver/stress/RxWriteQueryInTx.java +++ b/driver/src/test/java/org/neo4j/driver/stress/RxWriteQueryInTx.java @@ -46,7 +46,8 @@ public CompletionStage execute( C context ) { CompletableFuture queryFinished = new CompletableFuture<>(); RxSession session = newSession( AccessMode.WRITE, context ); - Flux.usingWhen( session.beginTransaction(), tx -> tx.run( "CREATE ()" ).summary(), RxTransaction::commit, RxTransaction::rollback ).subscribe( + Flux.usingWhen( session.beginTransaction(), tx -> tx.run( "CREATE ()" ).consume(), + RxTransaction::commit, ( tx, error ) -> tx.rollback(), null ).subscribe( summary -> { assertEquals( 1, summary.counters().nodesCreated() ); context.nodeCreated(); diff --git a/driver/src/test/java/org/neo4j/driver/stress/RxWriteQueryWithRetries.java b/driver/src/test/java/org/neo4j/driver/stress/RxWriteQueryWithRetries.java index 8f8b83ee10..9d654c4c6c 100644 --- a/driver/src/test/java/org/neo4j/driver/stress/RxWriteQueryWithRetries.java +++ b/driver/src/test/java/org/neo4j/driver/stress/RxWriteQueryWithRetries.java @@ -19,6 +19,7 @@ package org.neo4j.driver.stress; import reactor.core.publisher.Flux; +import reactor.core.publisher.Mono; import java.util.concurrent.CompletableFuture; import java.util.concurrent.CompletionStage; @@ -44,8 +45,8 @@ public RxWriteQueryWithRetries( AbstractStressTestBase stressTest, Driver dri public CompletionStage execute( C context ) { CompletableFuture queryFinished = new CompletableFuture<>(); - Flux.using( () -> newSession( AccessMode.READ, context ), - session -> session.writeTransaction( tx -> tx.run( "CREATE ()" ).summary() ), RxSession::close ) + Flux.usingWhen( Mono.fromSupplier( () -> newSession( AccessMode.READ, context ) ), + session -> session.writeTransaction( tx -> tx.run( "CREATE ()" ).consume() ), RxSession::close ) .subscribe( summary -> { queryFinished.complete( null ); assertEquals( 1, summary.counters().nodesCreated() ); diff --git a/driver/src/test/java/org/neo4j/driver/stress/SessionPoolingStressIT.java b/driver/src/test/java/org/neo4j/driver/stress/SessionPoolingStressIT.java index fe6e20e164..7deeb20eec 100644 --- a/driver/src/test/java/org/neo4j/driver/stress/SessionPoolingStressIT.java +++ b/driver/src/test/java/org/neo4j/driver/stress/SessionPoolingStressIT.java @@ -160,7 +160,7 @@ private void runQuery( String query ) throws InterruptedException { StatementResult run = session.run( query ); Thread.sleep( random.nextInt( 100 ) ); - run.summary(); + run.consume(); Thread.sleep( random.nextInt( 100 ) ); } } diff --git a/examples/src/main/java/org/neo4j/docs/driver/RxAutocommitTransactionExample.java b/examples/src/main/java/org/neo4j/docs/driver/RxAutocommitTransactionExample.java index beccc376f1..16bfcb830c 100644 --- a/examples/src/main/java/org/neo4j/docs/driver/RxAutocommitTransactionExample.java +++ b/examples/src/main/java/org/neo4j/docs/driver/RxAutocommitTransactionExample.java @@ -20,6 +20,7 @@ import io.reactivex.Flowable; import reactor.core.publisher.Flux; +import reactor.core.publisher.Mono; import java.util.Collections; import java.util.Map; @@ -39,7 +40,7 @@ public Flux readProductTitlesReactor() String query = "MATCH (p:Product) WHERE p.id = $id RETURN p.title"; Map parameters = Collections.singletonMap( "id", 0 ); - return Flux.using( driver::rxSession, + return Flux.usingWhen( Mono.fromSupplier( driver::rxSession ), session -> Flux.from( session.run( query, parameters ).records() ).map( record -> record.get( 0 ).asString() ), RxSession::close ); // end::reactor-autocommit-transaction[] @@ -51,9 +52,13 @@ public Flowable readProductTitlesRxJava() String query = "MATCH (p:Product) WHERE p.id = $id RETURN p.title"; Map parameters = Collections.singletonMap( "id", 0 ); - return Flowable.using( driver::rxSession, - session -> Flowable.fromPublisher( session.run( query, parameters ).records() ).map( record -> record.get( 0 ).asString() ), - RxSession::close ); + RxSession session = driver.rxSession(); + return Flowable.fromPublisher( session.run( query, parameters ).records() ).map( record -> record.get( 0 ).asString() ) + // It is okay to skip session.close() when publisher is completed successfully or cancelled + .onErrorResumeNext( error -> { + // We still rethrows the original error here. In a real application, you may want to handle the error directly here. + return Flowable.fromPublisher( session.close() ).concatWith( Flowable.error( error ) ); + } ); // end::RxJava-autocommit-transaction[] } } diff --git a/examples/src/main/java/org/neo4j/docs/driver/RxExplicitTransactionExample.java b/examples/src/main/java/org/neo4j/docs/driver/RxExplicitTransactionExample.java index 300bf2bf89..49d0facaae 100644 --- a/examples/src/main/java/org/neo4j/docs/driver/RxExplicitTransactionExample.java +++ b/examples/src/main/java/org/neo4j/docs/driver/RxExplicitTransactionExample.java @@ -41,12 +41,9 @@ public Flux readSingleProductReactor() Map parameters = Collections.singletonMap( "id", 0 ); RxSession session = driver.rxSession(); - // It is recommended to use Flux.usingWhen for explicit transactions and Flux.using for autocommit transactions (session). - // This is because an explicit transaction needs to be supplied via a another resource publisher session.beginTransaction. return Flux.usingWhen( session.beginTransaction(), tx -> Flux.from( tx.run( query, parameters ).records() ).map( record -> record.get( 0 ).asString() ), - RxTransaction::commit, - RxTransaction::rollback ); + RxTransaction::commit, ( tx, error ) -> tx.rollback(), null ); // end::reactor-explicit-transaction[] } @@ -58,9 +55,14 @@ public Flowable readSingleProductRxJava() RxSession session = driver.rxSession(); return Flowable.fromPublisher( session.beginTransaction() ) - .flatMap( tx -> Flowable.fromPublisher( tx.run( query, parameters ).records() ).map( record -> record.get( 0 ).asString() ) - .doOnComplete( tx::commit ) - .doOnError( error -> tx.rollback() ) ); + .flatMap( tx -> + Flowable.fromPublisher( tx.run( query, parameters ).records() ) + .map( record -> record.get( 0 ).asString() ) + .concatWith( tx.commit() ) + .onErrorResumeNext( error -> { + // We rollback and rethrow the error. For a real application, you may want to handle the error directly here + return Flowable.fromPublisher( tx.rollback() ).concatWith( Flowable.error( error ) ); + } ) ); // end::RxJava-explicit-transaction[] } } diff --git a/examples/src/main/java/org/neo4j/docs/driver/RxTransactionFunctionExample.java b/examples/src/main/java/org/neo4j/docs/driver/RxTransactionFunctionExample.java index 2a51dbe2e0..ae3c0aeb06 100644 --- a/examples/src/main/java/org/neo4j/docs/driver/RxTransactionFunctionExample.java +++ b/examples/src/main/java/org/neo4j/docs/driver/RxTransactionFunctionExample.java @@ -42,11 +42,11 @@ public Flux printAllProductsReactor() String query = "MATCH (p:Product) WHERE p.id = $id RETURN p.title"; Map parameters = Collections.singletonMap( "id", 0 ); - - return Flux.using( driver::rxSession, session -> session.readTransaction( tx -> { + return Flux.usingWhen( Mono.fromSupplier( driver::rxSession ), + session -> session.readTransaction( tx -> { RxStatementResult result = tx.run( query, parameters ); return Flux.from( result.records() ) - .doOnNext( record -> System.out.println( record.get( 0 ).asString() ) ).then( Mono.from( result.summary() ) ); + .doOnNext( record -> System.out.println( record.get( 0 ).asString() ) ).then( Mono.from( result.consume() ) ); } ), RxSession::close ); // end::reactor-transaction-function[] @@ -58,13 +58,15 @@ public Flowable printAllProductsRxJava() String query = "MATCH (p:Product) WHERE p.id = $id RETURN p.title"; Map parameters = Collections.singletonMap( "id", 0 ); - - return Flowable.using( driver::rxSession, session -> session.readTransaction( tx -> { + RxSession session = driver.rxSession(); + return Flowable.fromPublisher( session.readTransaction( tx -> { RxStatementResult result = tx.run( query, parameters ); return Flowable.fromPublisher( result.records() ) - .doOnNext( record -> System.out.println( record.get( 0 ).asString() ) ).ignoreElements().andThen( result.summary() ); - } - ), RxSession::close ); + .doOnNext( record -> System.out.println( record.get( 0 ).asString() ) ).ignoreElements().andThen( result.consume() ); + } ) ).onErrorResumeNext( error -> { + // We rollback and rethrow the error. For a real application, you may want to handle the error directly here + return Flowable.fromPublisher( session.close() ).concatWith( Flowable.error( error ) ); + } ); // end::RxJava-transaction-function[] } }