diff --git a/driver/src/main/java/org/neo4j/driver/Config.java b/driver/src/main/java/org/neo4j/driver/Config.java index dafbbdc1bc..382bda8770 100644 --- a/driver/src/main/java/org/neo4j/driver/Config.java +++ b/driver/src/main/java/org/neo4j/driver/Config.java @@ -18,19 +18,24 @@ */ package org.neo4j.driver; +import org.reactivestreams.Subscription; + import java.io.File; import java.net.InetAddress; import java.util.Objects; import java.util.concurrent.TimeUnit; import java.util.logging.Level; +import org.neo4j.driver.async.AsyncSession; import org.neo4j.driver.exceptions.ServiceUnavailableException; import org.neo4j.driver.exceptions.SessionExpiredException; import org.neo4j.driver.exceptions.TransientException; import org.neo4j.driver.internal.async.pool.PoolSettings; import org.neo4j.driver.internal.cluster.RoutingSettings; +import org.neo4j.driver.internal.handlers.pulln.FetchSizeUtil; import org.neo4j.driver.internal.retry.RetrySettings; import org.neo4j.driver.net.ServerAddressResolver; +import org.neo4j.driver.reactive.RxSession; import org.neo4j.driver.util.Immutable; import org.neo4j.driver.util.Resource; @@ -88,6 +93,7 @@ public class Config private final int routingFailureLimit; private final long routingRetryDelayMillis; + private final long fetchSize; private long routingTablePurgeDelayMillis; private final int connectionTimeoutMillis; @@ -114,6 +120,7 @@ private Config( ConfigBuilder builder ) this.routingTablePurgeDelayMillis = builder.routingTablePurgeDelayMillis; this.retrySettings = builder.retrySettings; this.resolver = builder.resolver; + this.fetchSize = builder.fetchSize; this.isMetricsEnabled = builder.isMetricsEnabled; } @@ -230,6 +237,11 @@ RetrySettings retrySettings() return retrySettings; } + public long fetchSize() + { + return fetchSize; + } + /** * @return if the metrics is enabled or not on this driver. */ @@ -258,7 +270,7 @@ public static class ConfigBuilder private RetrySettings retrySettings = RetrySettings.DEFAULT; private ServerAddressResolver resolver; private boolean isMetricsEnabled = false; - + private long fetchSize = FetchSizeUtil.DEFAULT_FETCH_SIZE; private ConfigBuilder() {} @@ -566,6 +578,26 @@ public ConfigBuilder withRoutingTablePurgeDelay( long delay, TimeUnit unit ) return this; } + /** + * Specify how many records to fetch in each batch. + * This config is only valid when the driver is used with servers that support Bolt V4 (Server version 4.0 and later). + * + * Bolt V4 enables pulling records in batches to allow client to take control of data population and apply back pressure to server. + * This config specifies the default fetch size for all query runs using {@link Session} and {@link AsyncSession}. + * By default, the value is set to {@code 1000}. + * Use {@code -1} to disables back pressure and config client to pull all records at once after each run. + * + * This config only applies to run result obtained via {@link Session} and {@link AsyncSession}. + * As with {@link RxSession}, the batch size is provided via {@link Subscription#request(long)} instead. + * @param size the default record fetch size when pulling records in batches using Bolt V4. + * @return this builder + */ + public ConfigBuilder withFetchSize( long size ) + { + this.fetchSize = FetchSizeUtil.assertValidFetchSize( size ); + return this; + } + /** * Specify socket connection timeout. *

diff --git a/driver/src/main/java/org/neo4j/driver/SessionConfig.java b/driver/src/main/java/org/neo4j/driver/SessionConfig.java index bb8866193b..7a164750bc 100644 --- a/driver/src/main/java/org/neo4j/driver/SessionConfig.java +++ b/driver/src/main/java/org/neo4j/driver/SessionConfig.java @@ -18,6 +18,8 @@ */ package org.neo4j.driver; +import org.reactivestreams.Subscription; + import java.util.Arrays; import java.util.Objects; import java.util.Optional; @@ -26,6 +28,7 @@ import org.neo4j.driver.reactive.RxSession; import static java.util.Objects.requireNonNull; +import static org.neo4j.driver.internal.handlers.pulln.FetchSizeUtil.assertValidFetchSize; /** * The session configurations used to configure a session. @@ -37,12 +40,14 @@ public class SessionConfig private final Iterable bookmarks; private final AccessMode defaultAccessMode; private final String database; + private final Optional fetchSize; private SessionConfig( Builder builder ) { this.bookmarks = builder.bookmarks; this.defaultAccessMode = builder.defaultAccessMode; this.database = builder.database; + this.fetchSize = builder.fetchSize; } /** @@ -109,6 +114,15 @@ public Optional database() return Optional.ofNullable( database ); } + /** + * This value if set, overrides the default fetch size set on {@link Config#fetchSize()}. + * @return an optional value of fetch size. + */ + public Optional fetchSize() + { + return fetchSize; + } + @Override public boolean equals( Object o ) { @@ -121,7 +135,8 @@ public boolean equals( Object o ) return false; } SessionConfig that = (SessionConfig) o; - return Objects.equals( bookmarks, that.bookmarks ) && defaultAccessMode == that.defaultAccessMode && Objects.equals( database, that.database ); + return Objects.equals( bookmarks, that.bookmarks ) && defaultAccessMode == that.defaultAccessMode && Objects.equals( database, that.database ) + && Objects.equals( fetchSize, that.fetchSize ); } @Override @@ -133,7 +148,8 @@ public int hashCode() @Override public String toString() { - return "SessionParameters{" + "bookmarks=" + bookmarks + ", defaultAccessMode=" + defaultAccessMode + ", database='" + database + '\'' + '}'; + return "SessionParameters{" + "bookmarks=" + bookmarks + ", defaultAccessMode=" + defaultAccessMode + ", database='" + database + '\'' + + ", fetchSize=" + fetchSize + '}'; } /** @@ -141,6 +157,7 @@ public String toString() */ public static class Builder { + private Optional fetchSize = Optional.empty(); private Iterable bookmarks = null; private AccessMode defaultAccessMode = AccessMode.WRITE; private String database = null; @@ -230,6 +247,27 @@ public Builder withDatabase( String database ) return this; } + /** + * Specify how many records to fetch in each batch for this session. + * This config will overrides the default value set on {@link Config#fetchSize()}. + * This config is only valid when the driver is used with servers that support Bolt V4 (Server version 4.0 and later). + * + * Bolt V4 enables pulling records in batches to allow client to take control of data population and apply back pressure to server. + * This config specifies the default fetch size for all query runs using {@link Session} and {@link AsyncSession}. + * By default, the value is set to {@code 1000}. + * Use {@code -1} to disables back pressure and config client to pull all records at once after each run. + * + * This config only applies to run result obtained via {@link Session} and {@link AsyncSession}. + * As with {@link RxSession}, the batch size is provided via {@link Subscription#request(long)} instead. + * @param size the default record fetch size when pulling records in batches using Bolt V4. + * @return this builder + */ + public Builder withFetchSize( long size ) + { + this.fetchSize = Optional.of( assertValidFetchSize( size ) ); + return this; + } + public SessionConfig build() { return new SessionConfig( this ); diff --git a/driver/src/main/java/org/neo4j/driver/StatementResult.java b/driver/src/main/java/org/neo4j/driver/StatementResult.java index 912f2d1c0c..1feb142ead 100644 --- a/driver/src/main/java/org/neo4j/driver/StatementResult.java +++ b/driver/src/main/java/org/neo4j/driver/StatementResult.java @@ -20,11 +20,11 @@ import java.util.Iterator; import java.util.List; +import java.util.function.Function; import java.util.stream.Stream; import org.neo4j.driver.exceptions.NoSuchRecordException; import org.neo4j.driver.summary.ResultSummary; -import java.util.function.Function; import org.neo4j.driver.util.Resource; @@ -138,29 +138,12 @@ public interface StatementResult extends Iterator */ List list( Function mapFunction ); - /** - * Consume the entire result, yielding a summary of it. - * - * Calling this method exhausts the result. - * - *

-     * {@code
-     * ResultSummary summary = session.run( "PROFILE MATCH (n:User {id: 12345}) RETURN n" ).consume();
-     * }
-     * 
- * - * @return a summary for the whole query result - */ - ResultSummary consume(); - /** * Return the result summary. * - * If the records in the result is not fully consumed, then calling this method will force to pull all remaining - * records into buffer to yield the summary. + * If the records in the result is not fully consumed, then calling this method will exhausts the result. * - * If you want to obtain the summary but discard the records, use - * {@link StatementResult#consume()} instead. + * If you want to access unconsumed records after summary, you shall use {@link StatementResult#list()} to buffer all records into memory before summary. * * @return a summary for the whole query result. */ 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 0fca6067ef..c1a6763503 100644 --- a/driver/src/main/java/org/neo4j/driver/async/StatementResultCursor.java +++ b/driver/src/main/java/org/neo4j/driver/async/StatementResultCursor.java @@ -27,6 +27,7 @@ import org.neo4j.driver.Record; import org.neo4j.driver.Records; +import org.neo4j.driver.StatementResult; import org.neo4j.driver.exceptions.NoSuchRecordException; import org.neo4j.driver.summary.ResultSummary; @@ -72,10 +73,9 @@ public interface StatementResultCursor /** * Asynchronously retrieve the result summary. *

- * If the records in the result is not fully consumed, then calling this method will force to pull all remaining - * records into buffer to yield the summary. + * If the records in the result is not fully consumed, then calling this method will exhausts the result. *

- * If you want to obtain the summary but discard the records, use {@link #consumeAsync()} instead. + * If you want to access unconsumed records after summary, you shall use {@link StatementResult#list()} to buffer all records into memory before summary. * * @return a {@link CompletionStage} completed with a summary for the whole query result. Stage can also be * completed exceptionally if query execution fails. @@ -110,14 +110,6 @@ public interface StatementResultCursor */ CompletionStage singleAsync(); - /** - * Asynchronously consume the entire result, yielding a summary of it. Calling this method exhausts the result. - * - * @return a {@link CompletionStage} completed with a summary for the whole query result. Stage can also be - * completed exceptionally if query execution fails. - */ - CompletionStage consumeAsync(); - /** * Asynchronously apply the given {@link Consumer action} to every record in the result, yielding a summary of it. * 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 b650a2184d..916572b4ad 100644 --- a/driver/src/main/java/org/neo4j/driver/internal/FailableCursor.java +++ b/driver/src/main/java/org/neo4j/driver/internal/FailableCursor.java @@ -22,5 +22,6 @@ public interface FailableCursor { + CompletionStage consumeAsync(); CompletionStage failureAsync(); } diff --git a/driver/src/main/java/org/neo4j/driver/internal/InternalDriver.java b/driver/src/main/java/org/neo4j/driver/internal/InternalDriver.java index 786a979cb7..05fa2b8c65 100644 --- a/driver/src/main/java/org/neo4j/driver/internal/InternalDriver.java +++ b/driver/src/main/java/org/neo4j/driver/internal/InternalDriver.java @@ -164,10 +164,10 @@ private static RuntimeException driverCloseException() return new IllegalStateException( "This driver instance has already been closed" ); } - public NetworkSession newSession( SessionConfig parameters ) + public NetworkSession newSession( SessionConfig config ) { assertOpen(); - NetworkSession session = sessionFactory.newInstance( parameters ); + NetworkSession session = sessionFactory.newInstance( config ); if ( closed.get() ) { // session does not immediately acquire connection, it is fine to just throw 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 a836a074cf..bc2532346e 100644 --- a/driver/src/main/java/org/neo4j/driver/internal/InternalStatementResult.java +++ b/driver/src/main/java/org/neo4j/driver/internal/InternalStatementResult.java @@ -22,18 +22,18 @@ import java.util.Spliterator; import java.util.Spliterators; import java.util.concurrent.CompletionStage; +import java.util.function.Function; import java.util.stream.Stream; import java.util.stream.StreamSupport; -import org.neo4j.driver.internal.spi.Connection; -import org.neo4j.driver.internal.util.Futures; import org.neo4j.driver.Record; import org.neo4j.driver.StatementResult; import org.neo4j.driver.async.StatementResultCursor; import org.neo4j.driver.exceptions.ClientException; import org.neo4j.driver.exceptions.NoSuchRecordException; +import org.neo4j.driver.internal.spi.Connection; +import org.neo4j.driver.internal.util.Futures; import org.neo4j.driver.summary.ResultSummary; -import java.util.function.Function; public class InternalStatementResult implements StatementResult { @@ -111,12 +111,6 @@ public List list( Function mapFunction ) return blockingGet( cursor.listAsync( mapFunction ) ); } - @Override - public ResultSummary consume() - { - return blockingGet( cursor.consumeAsync() ); - } - @Override public ResultSummary summary() { diff --git a/driver/src/main/java/org/neo4j/driver/internal/SessionFactory.java b/driver/src/main/java/org/neo4j/driver/internal/SessionFactory.java index 3df56fff2c..60c6c72c43 100644 --- a/driver/src/main/java/org/neo4j/driver/internal/SessionFactory.java +++ b/driver/src/main/java/org/neo4j/driver/internal/SessionFactory.java @@ -25,7 +25,7 @@ public interface SessionFactory { - NetworkSession newInstance( SessionConfig parameters ); + NetworkSession newInstance( SessionConfig sessionConfig ); CompletionStage verifyConnectivity(); diff --git a/driver/src/main/java/org/neo4j/driver/internal/SessionFactoryImpl.java b/driver/src/main/java/org/neo4j/driver/internal/SessionFactoryImpl.java index 157b98bbca..46d8d1da04 100644 --- a/driver/src/main/java/org/neo4j/driver/internal/SessionFactoryImpl.java +++ b/driver/src/main/java/org/neo4j/driver/internal/SessionFactoryImpl.java @@ -36,6 +36,7 @@ public class SessionFactoryImpl implements SessionFactory private final RetryLogic retryLogic; private final Logging logging; private final boolean leakedSessionsLoggingEnabled; + private final long defaultFetchSize; SessionFactoryImpl( ConnectionProvider connectionProvider, RetryLogic retryLogic, Config config ) { @@ -43,6 +44,7 @@ public class SessionFactoryImpl implements SessionFactory this.leakedSessionsLoggingEnabled = config.logLeakedSessions(); this.retryLogic = retryLogic; this.logging = config.logging(); + this.defaultFetchSize = config.fetchSize(); } @Override @@ -50,7 +52,12 @@ public NetworkSession newInstance( SessionConfig sessionConfig ) { BookmarkHolder bookmarkHolder = new DefaultBookmarkHolder( InternalBookmark.from( sessionConfig.bookmarks() ) ); return createSession( connectionProvider, retryLogic, parseDatabaseName( sessionConfig ), - sessionConfig.defaultAccessMode(), bookmarkHolder, logging ); + sessionConfig.defaultAccessMode(), bookmarkHolder, parseFetchSize( sessionConfig ), logging ); + } + + private long parseFetchSize( SessionConfig sessionConfig ) + { + return sessionConfig.fetchSize().orElse( defaultFetchSize ); } private DatabaseName parseDatabaseName( SessionConfig sessionConfig ) @@ -85,10 +92,10 @@ public ConnectionProvider getConnectionProvider() } private NetworkSession createSession( ConnectionProvider connectionProvider, RetryLogic retryLogic, DatabaseName databaseName, AccessMode mode, - BookmarkHolder bookmarkHolder, Logging logging ) + BookmarkHolder bookmarkHolder, long fetchSize, Logging logging ) { return leakedSessionsLoggingEnabled - ? new LeakLoggingNetworkSession( connectionProvider, retryLogic, databaseName, mode, bookmarkHolder, logging ) - : new NetworkSession( connectionProvider, retryLogic, databaseName, mode, bookmarkHolder, logging ); + ? new LeakLoggingNetworkSession( connectionProvider, retryLogic, databaseName, mode, bookmarkHolder, fetchSize, logging ) + : new NetworkSession( connectionProvider, retryLogic, databaseName, mode, bookmarkHolder, fetchSize, logging ); } } diff --git a/driver/src/main/java/org/neo4j/driver/internal/async/ExplicitTransaction.java b/driver/src/main/java/org/neo4j/driver/internal/async/ExplicitTransaction.java index e8d613fa81..a5c9e3d6e6 100644 --- a/driver/src/main/java/org/neo4j/driver/internal/async/ExplicitTransaction.java +++ b/driver/src/main/java/org/neo4j/driver/internal/async/ExplicitTransaction.java @@ -29,7 +29,7 @@ import org.neo4j.driver.exceptions.ClientException; import org.neo4j.driver.internal.BookmarkHolder; import org.neo4j.driver.internal.InternalBookmark; -import org.neo4j.driver.internal.cursor.InternalStatementResultCursor; +import org.neo4j.driver.internal.cursor.AsyncStatementResultCursor; import org.neo4j.driver.internal.cursor.RxStatementResultCursor; import org.neo4j.driver.internal.messaging.BoltProtocol; import org.neo4j.driver.internal.spi.Connection; @@ -62,15 +62,17 @@ private enum State private final BoltProtocol protocol; private final BookmarkHolder bookmarkHolder; private final ResultCursorsHolder resultCursors; + private final long fetchSize; private volatile State state = State.ACTIVE; - public ExplicitTransaction( Connection connection, BookmarkHolder bookmarkHolder ) + public ExplicitTransaction( Connection connection, BookmarkHolder bookmarkHolder, long fetchSize ) { this.connection = connection; this.protocol = connection.protocol(); this.bookmarkHolder = bookmarkHolder; this.resultCursors = new ResultCursorsHolder(); + this.fetchSize = fetchSize; } public CompletionStage beginAsync( InternalBookmark initialBookmark, TransactionConfig config ) @@ -139,8 +141,8 @@ else if ( state == State.ROLLED_BACK ) public CompletionStage runAsync( Statement statement, boolean waitForRunResponse ) { ensureCanRunQueries(); - CompletionStage cursorStage = - protocol.runInExplicitTransaction( connection, statement, this, waitForRunResponse ).asyncResult(); + CompletionStage cursorStage = + protocol.runInExplicitTransaction( connection, statement, this, waitForRunResponse, fetchSize ).asyncResult(); resultCursors.add( cursorStage ); return cursorStage.thenApply( cursor -> cursor ); } @@ -149,7 +151,7 @@ public CompletionStage runRx( Statement statement ) { ensureCanRunQueries(); CompletionStage cursorStage = - protocol.runInExplicitTransaction( connection, statement, this, false ).rxResult(); + protocol.runInExplicitTransaction( connection, statement, this, false, fetchSize ).rxResult(); resultCursors.add( cursorStage ); return cursorStage; } diff --git a/driver/src/main/java/org/neo4j/driver/internal/async/LeakLoggingNetworkSession.java b/driver/src/main/java/org/neo4j/driver/internal/async/LeakLoggingNetworkSession.java index 476e233296..adf4495d2c 100644 --- a/driver/src/main/java/org/neo4j/driver/internal/async/LeakLoggingNetworkSession.java +++ b/driver/src/main/java/org/neo4j/driver/internal/async/LeakLoggingNetworkSession.java @@ -33,9 +33,9 @@ public class LeakLoggingNetworkSession extends NetworkSession private final String stackTrace; public LeakLoggingNetworkSession( ConnectionProvider connectionProvider, RetryLogic retryLogic, DatabaseName databaseName, AccessMode mode, - BookmarkHolder bookmarkHolder, Logging logging ) + BookmarkHolder bookmarkHolder, long fetchSize, Logging logging ) { - super( connectionProvider, retryLogic, databaseName, mode, bookmarkHolder, logging ); + super( connectionProvider, retryLogic, databaseName, mode, bookmarkHolder, fetchSize, logging ); this.stackTrace = captureStackTrace(); } 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 4d23f2820e..e0c3632ee7 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 @@ -34,7 +34,7 @@ import org.neo4j.driver.internal.DatabaseName; import org.neo4j.driver.internal.FailableCursor; import org.neo4j.driver.internal.InternalBookmark; -import org.neo4j.driver.internal.cursor.InternalStatementResultCursor; +import org.neo4j.driver.internal.cursor.AsyncStatementResultCursor; import org.neo4j.driver.internal.cursor.RxStatementResultCursor; import org.neo4j.driver.internal.cursor.StatementResultCursorFactory; import org.neo4j.driver.internal.logging.PrefixedLogger; @@ -57,6 +57,7 @@ public class NetworkSession protected final Logger logger; private final BookmarkHolder bookmarkHolder; + private final long fetchSize; private volatile CompletionStage transactionStage = completedWithNull(); private volatile CompletionStage connectionStage = completedWithNull(); private volatile CompletionStage resultCursorStage = completedWithNull(); @@ -64,7 +65,7 @@ public class NetworkSession private final AtomicBoolean open = new AtomicBoolean( true ); public NetworkSession( ConnectionProvider connectionProvider, RetryLogic retryLogic, DatabaseName databaseName, AccessMode mode, - BookmarkHolder bookmarkHolder, Logging logging ) + BookmarkHolder bookmarkHolder, long fetchSize, Logging logging ) { this.connectionProvider = connectionProvider; this.mode = mode; @@ -72,11 +73,12 @@ public NetworkSession( ConnectionProvider connectionProvider, RetryLogic retryLo this.logger = new PrefixedLogger( "[" + hashCode() + "]", logging.getLog( LOG_NAME ) ); this.bookmarkHolder = bookmarkHolder; this.connectionContext = new NetworkSessionConnectionContext( databaseName, bookmarkHolder.getBookmark() ); + this.fetchSize = fetchSize; } public CompletionStage runAsync( Statement statement, TransactionConfig config, boolean waitForRunResponse ) { - CompletionStage newResultCursorStage = + CompletionStage newResultCursorStage = buildResultCursorFactory( statement, config, waitForRunResponse ).thenCompose( StatementResultCursorFactory::asyncResult ); resultCursorStage = newResultCursorStage.exceptionally( error -> null ); @@ -106,7 +108,7 @@ public CompletionStage beginTransactionAsync( AccessMode mo .thenCompose( ignore -> acquireConnection( mode ) ) .thenCompose( connection -> { - ExplicitTransaction tx = new ExplicitTransaction( connection, bookmarkHolder ); + ExplicitTransaction tx = new ExplicitTransaction( connection, bookmarkHolder, fetchSize ); return tx.beginAsync( bookmarkHolder.getBookmark(), config ); } ); @@ -194,7 +196,7 @@ public CompletionStage closeAsync() if ( cursor != null ) { // there exists a cursor with potentially unconsumed error, try to extract and propagate it - return cursor.failureAsync(); + return cursor.consumeAsync(); } // no result cursor exists so no error exists return completedWithNull(); @@ -231,7 +233,7 @@ private CompletionStage buildResultCursorFactory( try { StatementResultCursorFactory factory = connection.protocol() - .runInAutoCommitTransaction( connection, statement, bookmarkHolder, config, waitForRunResponse ); + .runInAutoCommitTransaction( connection, statement, bookmarkHolder, config, waitForRunResponse, fetchSize ); return completedFuture( factory ); } catch ( Throwable e ) 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 319f576fac..355c16ad25 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.failureAsync() ); + .thenCompose( cursor -> cursor == null ? completedWithNull() : cursor.consumeAsync() ); } } diff --git a/driver/src/main/java/org/neo4j/driver/internal/cluster/RoutingProcedureRunner.java b/driver/src/main/java/org/neo4j/driver/internal/cluster/RoutingProcedureRunner.java index b9c8a74d68..5746fabe3b 100644 --- a/driver/src/main/java/org/neo4j/driver/internal/cluster/RoutingProcedureRunner.java +++ b/driver/src/main/java/org/neo4j/driver/internal/cluster/RoutingProcedureRunner.java @@ -39,6 +39,7 @@ import static org.neo4j.driver.Values.parameters; import static org.neo4j.driver.internal.DatabaseNameUtil.defaultDatabase; +import static org.neo4j.driver.internal.handlers.pulln.FetchSizeUtil.UNLIMITED_FETCH_SIZE; public class RoutingProcedureRunner { @@ -86,7 +87,7 @@ BookmarkHolder bookmarkHolder( InternalBookmark ignored ) CompletionStage> runProcedure( Connection connection, Statement procedure, BookmarkHolder bookmarkHolder ) { return connection.protocol() - .runInAutoCommitTransaction( connection, procedure, bookmarkHolder, TransactionConfig.empty(), true ) + .runInAutoCommitTransaction( connection, procedure, bookmarkHolder, TransactionConfig.empty(), true, UNLIMITED_FETCH_SIZE ) .asyncResult().thenCompose( StatementResultCursor::listAsync ); } diff --git a/driver/src/main/java/org/neo4j/driver/internal/cursor/InternalStatementResultCursor.java b/driver/src/main/java/org/neo4j/driver/internal/cursor/AsyncStatementResultCursor.java similarity index 89% rename from driver/src/main/java/org/neo4j/driver/internal/cursor/InternalStatementResultCursor.java rename to driver/src/main/java/org/neo4j/driver/internal/cursor/AsyncStatementResultCursor.java index 4560931daf..eefa25a3ee 100644 --- a/driver/src/main/java/org/neo4j/driver/internal/cursor/InternalStatementResultCursor.java +++ b/driver/src/main/java/org/neo4j/driver/internal/cursor/AsyncStatementResultCursor.java @@ -21,6 +21,6 @@ import org.neo4j.driver.internal.FailableCursor; import org.neo4j.driver.async.StatementResultCursor; -public interface InternalStatementResultCursor extends StatementResultCursor, FailableCursor +public interface AsyncStatementResultCursor extends StatementResultCursor, FailableCursor { } diff --git a/driver/src/main/java/org/neo4j/driver/internal/async/AsyncStatementResultCursor.java b/driver/src/main/java/org/neo4j/driver/internal/cursor/AsyncStatementResultCursorImpl.java similarity index 88% rename from driver/src/main/java/org/neo4j/driver/internal/async/AsyncStatementResultCursor.java rename to driver/src/main/java/org/neo4j/driver/internal/cursor/AsyncStatementResultCursorImpl.java index dd82c5f2cd..97ba5cd031 100644 --- a/driver/src/main/java/org/neo4j/driver/internal/async/AsyncStatementResultCursor.java +++ b/driver/src/main/java/org/neo4j/driver/internal/cursor/AsyncStatementResultCursorImpl.java @@ -16,7 +16,7 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -package org.neo4j.driver.internal.async; +package org.neo4j.driver.internal.cursor; import java.util.List; import java.util.concurrent.CompletableFuture; @@ -24,29 +24,28 @@ import java.util.function.Consumer; import java.util.function.Function; +import org.neo4j.driver.Record; +import org.neo4j.driver.exceptions.NoSuchRecordException; import org.neo4j.driver.internal.handlers.PullAllResponseHandler; import org.neo4j.driver.internal.handlers.RunResponseHandler; import org.neo4j.driver.internal.util.Futures; -import org.neo4j.driver.internal.cursor.InternalStatementResultCursor; -import org.neo4j.driver.Record; -import org.neo4j.driver.exceptions.NoSuchRecordException; import org.neo4j.driver.summary.ResultSummary; -public class AsyncStatementResultCursor implements InternalStatementResultCursor +public class AsyncStatementResultCursorImpl implements AsyncStatementResultCursor { - private final RunResponseHandler runResponseHandler; + private final RunResponseHandler runHandler; private final PullAllResponseHandler pullAllHandler; - public AsyncStatementResultCursor( RunResponseHandler runResponseHandler, PullAllResponseHandler pullAllHandler ) + public AsyncStatementResultCursorImpl( RunResponseHandler runHandler, PullAllResponseHandler pullAllHandler ) { - this.runResponseHandler = runResponseHandler; + this.runHandler = runHandler; this.pullAllHandler = pullAllHandler; } @Override public List keys() { - return runResponseHandler.statementKeys(); + return runHandler.statementKeys(); } @Override @@ -91,12 +90,6 @@ public CompletionStage singleAsync() } ); } - @Override - public CompletionStage consumeAsync() - { - return pullAllHandler.consumeAsync(); - } - @Override public CompletionStage forEachAsync( Consumer action ) { @@ -117,12 +110,19 @@ public CompletionStage> listAsync( Function mapFunction ) return pullAllHandler.listAsync( mapFunction ); } + @Override + public CompletionStage consumeAsync() + { + return pullAllHandler.summaryAsync().handle( ( summary, error ) -> error ); + } + @Override public CompletionStage failureAsync() { return pullAllHandler.failureAsync(); } + private void internalForEachAsync( Consumer action, CompletableFuture resultFuture ) { CompletionStage recordFuture = nextAsync(); diff --git a/driver/src/main/java/org/neo4j/driver/internal/cursor/AsyncResultCursorOnlyFactory.java b/driver/src/main/java/org/neo4j/driver/internal/cursor/AsyncStatementResultCursorOnlyFactory.java similarity index 81% rename from driver/src/main/java/org/neo4j/driver/internal/cursor/AsyncResultCursorOnlyFactory.java rename to driver/src/main/java/org/neo4j/driver/internal/cursor/AsyncStatementResultCursorOnlyFactory.java index 6d657afc7e..6752e0a249 100644 --- a/driver/src/main/java/org/neo4j/driver/internal/cursor/AsyncResultCursorOnlyFactory.java +++ b/driver/src/main/java/org/neo4j/driver/internal/cursor/AsyncStatementResultCursorOnlyFactory.java @@ -20,22 +20,20 @@ import java.util.concurrent.CompletionStage; -import org.neo4j.driver.internal.async.AsyncStatementResultCursor; +import org.neo4j.driver.exceptions.ClientException; import org.neo4j.driver.internal.handlers.PullAllResponseHandler; import org.neo4j.driver.internal.handlers.RunResponseHandler; import org.neo4j.driver.internal.messaging.Message; import org.neo4j.driver.internal.spi.Connection; import org.neo4j.driver.internal.util.Futures; -import org.neo4j.driver.exceptions.ClientException; import static java.util.Objects.requireNonNull; import static java.util.concurrent.CompletableFuture.completedFuture; -import static org.neo4j.driver.internal.messaging.request.PullAllMessage.PULL_ALL; /** * Used by Bolt V1, V2, V3 */ -public class AsyncResultCursorOnlyFactory implements StatementResultCursorFactory +public class AsyncStatementResultCursorOnlyFactory implements StatementResultCursorFactory { protected final Connection connection; protected final Message runMessage; @@ -43,7 +41,7 @@ public class AsyncResultCursorOnlyFactory implements StatementResultCursorFactor protected final PullAllResponseHandler pullAllHandler; private final boolean waitForRunResponse; - public AsyncResultCursorOnlyFactory( Connection connection, Message runMessage, RunResponseHandler runHandler, + public AsyncStatementResultCursorOnlyFactory( Connection connection, Message runMessage, RunResponseHandler runHandler, PullAllResponseHandler pullHandler, boolean waitForRunResponse ) { requireNonNull( connection ); @@ -59,19 +57,20 @@ public AsyncResultCursorOnlyFactory( Connection connection, Message runMessage, this.waitForRunResponse = waitForRunResponse; } - public CompletionStage asyncResult() + public CompletionStage asyncResult() { // only write and flush messages when async result is wanted. - connection.writeAndFlush( runMessage, runHandler, PULL_ALL, pullAllHandler ); + connection.write( runMessage, runHandler ); // queues the run message, will be flushed with pull message together + pullAllHandler.prePopulateRecords(); if ( waitForRunResponse ) { // wait for response of RUN before proceeding - return runHandler.runFuture().thenApply( ignore -> new AsyncStatementResultCursor( runHandler, pullAllHandler ) ); + return runHandler.runFuture().thenApply( ignore -> new AsyncStatementResultCursorImpl( runHandler, pullAllHandler ) ); } else { - return completedFuture( new AsyncStatementResultCursor( runHandler, pullAllHandler ) ); + return completedFuture( new AsyncStatementResultCursorImpl( runHandler, pullAllHandler ) ); } } diff --git a/driver/src/main/java/org/neo4j/driver/internal/cursor/RxStatementResultCursor.java b/driver/src/main/java/org/neo4j/driver/internal/cursor/RxStatementResultCursor.java index bf977e2d3d..62bd08daa9 100644 --- a/driver/src/main/java/org/neo4j/driver/internal/cursor/RxStatementResultCursor.java +++ b/driver/src/main/java/org/neo4j/driver/internal/cursor/RxStatementResultCursor.java @@ -21,122 +21,20 @@ import org.reactivestreams.Subscription; import java.util.List; -import java.util.Objects; -import java.util.concurrent.CompletableFuture; import java.util.concurrent.CompletionStage; import java.util.function.BiConsumer; -import org.neo4j.driver.internal.FailableCursor; -import org.neo4j.driver.internal.handlers.RunResponseHandler; -import org.neo4j.driver.internal.handlers.pulln.BasicPullResponseHandler; import org.neo4j.driver.Record; +import org.neo4j.driver.internal.FailableCursor; import org.neo4j.driver.summary.ResultSummary; -import static org.neo4j.driver.internal.handlers.pulln.AbstractBasicPullResponseHandler.DISCARD_RECORD_CONSUMER; - -public class RxStatementResultCursor implements Subscription, FailableCursor +public interface RxStatementResultCursor extends Subscription, FailableCursor { - private final RunResponseHandler runHandler; - private final BasicPullResponseHandler pullHandler; - private final Throwable runResponseError; - private final CompletableFuture summaryFuture = new CompletableFuture<>(); - private boolean isRecordHandlerInstalled = false; - - public RxStatementResultCursor( RunResponseHandler runHandler, BasicPullResponseHandler pullHandler ) - { - this( null, runHandler, pullHandler ); - } - - public RxStatementResultCursor( Throwable runError, RunResponseHandler runHandler, BasicPullResponseHandler pullHandler ) - { - Objects.requireNonNull( runHandler ); - Objects.requireNonNull( pullHandler ); - assertRunResponseArrived( runHandler ); - - this.runResponseError = runError; - this.runHandler = runHandler; - this.pullHandler = pullHandler; - installSummaryConsumer(); - } - - public List keys() - { - return runHandler.statementKeys(); - } - - public void installRecordConsumer( BiConsumer recordConsumer ) - { - if ( isRecordHandlerInstalled ) - { - return; - } - isRecordHandlerInstalled = true; - pullHandler.installRecordConsumer( recordConsumer ); - assertRunCompletedSuccessfully(); - } - - public void request( long n ) - { - pullHandler.request( n ); - } - - @Override - public void cancel() - { - pullHandler.cancel(); - } - - @Override - public CompletionStage failureAsync() - { - // calling this method will enforce discarding record stream and finish running cypher query - return summaryAsync().thenApply( summary -> (Throwable) null ).exceptionally( error -> error ); - } - - public CompletionStage summaryAsync() - { - if ( !isDone() ) // the summary is called before record streaming - { - installRecordConsumer( DISCARD_RECORD_CONSUMER ); - cancel(); - } - - return this.summaryFuture; - } - - public boolean isDone() - { - return summaryFuture.isDone(); - } + List keys(); - private void assertRunCompletedSuccessfully() - { - if ( runResponseError != null ) - { - pullHandler.onFailure( runResponseError ); - } - } + void installRecordConsumer( BiConsumer recordConsumer ); - private void installSummaryConsumer() - { - pullHandler.installSummaryConsumer( ( summary, error ) -> { - if ( error != null ) - { - summaryFuture.completeExceptionally( error ); - } - else if ( summary != null ) - { - summaryFuture.complete( summary ); - } - //else (null, null) to indicate a has_more success - } ); - } + CompletionStage summaryAsync(); - private void assertRunResponseArrived( RunResponseHandler runHandler ) - { - if ( !runHandler.runFuture().isDone() ) - { - throw new IllegalStateException( "Should wait for response of RUN before allowing PULL." ); - } - } + boolean isDone(); } 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 new file mode 100644 index 0000000000..689320a7c0 --- /dev/null +++ b/driver/src/main/java/org/neo4j/driver/internal/cursor/RxStatementResultCursorImpl.java @@ -0,0 +1,157 @@ +/* + * 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.Objects; +import java.util.concurrent.CompletableFuture; +import java.util.concurrent.CompletionStage; +import java.util.function.BiConsumer; + +import org.neo4j.driver.Record; +import org.neo4j.driver.internal.handlers.RunResponseHandler; +import org.neo4j.driver.internal.handlers.pulln.PullResponseHandler; +import org.neo4j.driver.summary.ResultSummary; + +public class RxStatementResultCursorImpl implements RxStatementResultCursor +{ + static final BiConsumer DISCARD_RECORD_CONSUMER = ( record, throwable ) -> {/*do nothing*/}; + private final RunResponseHandler runHandler; + private final PullResponseHandler pullHandler; + private final Throwable runResponseError; + private final CompletableFuture summaryFuture = new CompletableFuture<>(); + private BiConsumer recordConsumer; + + public RxStatementResultCursorImpl( RunResponseHandler runHandler, PullResponseHandler pullHandler ) + { + this( null, runHandler, pullHandler ); + } + + public RxStatementResultCursorImpl( Throwable runError, RunResponseHandler runHandler, PullResponseHandler pullHandler ) + { + Objects.requireNonNull( runHandler ); + Objects.requireNonNull( pullHandler ); + assertRunResponseArrived( runHandler ); + + this.runResponseError = runError; + this.runHandler = runHandler; + this.pullHandler = pullHandler; + installSummaryConsumer(); + } + + @Override + public List keys() + { + return runHandler.statementKeys(); + } + + @Override + public void installRecordConsumer( BiConsumer recordConsumer ) + { + if ( isRecordConsumerInstalled() ) + { + return; + } + this.recordConsumer = recordConsumer; + pullHandler.installRecordConsumer( this.recordConsumer ); + assertRunCompletedSuccessfully(); + } + + private boolean isRecordConsumerInstalled() + { + return this.recordConsumer != null; + } + + @Override + public void request( long n ) + { + pullHandler.request( n ); + } + + @Override + public void cancel() + { + pullHandler.cancel(); + } + + @Override + public CompletionStage consumeAsync() + { + // 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() + { + // It is safe to discard records as either the streaming has not started at all, or the streaming is fully finished. + return consumeAsync(); + } + + @Override + public CompletionStage summaryAsync() + { + if ( !isDone() ) // the summary is called before record streaming + { + installRecordConsumer( DISCARD_RECORD_CONSUMER ); + cancel(); + } + + return this.summaryFuture; + } + + @Override + public boolean isDone() + { + return summaryFuture.isDone(); + } + + private void assertRunCompletedSuccessfully() + { + if ( runResponseError != null ) + { + pullHandler.onFailure( runResponseError ); + } + } + + private void installSummaryConsumer() + { + pullHandler.installSummaryConsumer( ( summary, error ) -> { + if ( error != null && recordConsumer == DISCARD_RECORD_CONSUMER ) + { + // We will only report the error to summary if there is no user record consumer installed + // When a user record consumer is installed, the error will be reported to record consumer instead. + summaryFuture.completeExceptionally( error ); + } + else if ( summary != null ) + { + summaryFuture.complete( summary ); + } + //else (null, null) to indicate a has_more success + } ); + } + + private void assertRunResponseArrived( RunResponseHandler runHandler ) + { + if ( !runHandler.runFuture().isDone() ) + { + throw new IllegalStateException( "Should wait for response of RUN before allowing PULL." ); + } + } +} diff --git a/driver/src/main/java/org/neo4j/driver/internal/cursor/StatementResultCursorFactory.java b/driver/src/main/java/org/neo4j/driver/internal/cursor/StatementResultCursorFactory.java index 200d362925..9c0cf01b96 100644 --- a/driver/src/main/java/org/neo4j/driver/internal/cursor/StatementResultCursorFactory.java +++ b/driver/src/main/java/org/neo4j/driver/internal/cursor/StatementResultCursorFactory.java @@ -22,7 +22,7 @@ public interface StatementResultCursorFactory { - CompletionStage asyncResult(); + CompletionStage asyncResult(); CompletionStage rxResult(); } diff --git a/driver/src/main/java/org/neo4j/driver/internal/cursor/InternalStatementResultCursorFactory.java b/driver/src/main/java/org/neo4j/driver/internal/cursor/StatementResultCursorFactoryImpl.java similarity index 75% rename from driver/src/main/java/org/neo4j/driver/internal/cursor/InternalStatementResultCursorFactory.java rename to driver/src/main/java/org/neo4j/driver/internal/cursor/StatementResultCursorFactoryImpl.java index ab4fce646b..5259bc26cb 100644 --- a/driver/src/main/java/org/neo4j/driver/internal/cursor/InternalStatementResultCursorFactory.java +++ b/driver/src/main/java/org/neo4j/driver/internal/cursor/StatementResultCursorFactoryImpl.java @@ -20,28 +20,29 @@ import java.util.concurrent.CompletionStage; -import org.neo4j.driver.internal.async.AsyncStatementResultCursor; import org.neo4j.driver.internal.handlers.PullAllResponseHandler; import org.neo4j.driver.internal.handlers.RunResponseHandler; -import org.neo4j.driver.internal.handlers.pulln.BasicPullResponseHandler; +import org.neo4j.driver.internal.handlers.pulln.PullResponseHandler; import org.neo4j.driver.internal.messaging.Message; -import org.neo4j.driver.internal.messaging.request.PullMessage; import org.neo4j.driver.internal.spi.Connection; import static java.util.Objects.requireNonNull; import static java.util.concurrent.CompletableFuture.completedFuture; -public class InternalStatementResultCursorFactory implements StatementResultCursorFactory +/** + * Bolt V4 + */ +public class StatementResultCursorFactoryImpl implements StatementResultCursorFactory { private final RunResponseHandler runHandler; private final Connection connection; - private final BasicPullResponseHandler pullHandler; + private final PullResponseHandler pullHandler; private final PullAllResponseHandler pullAllHandler; private final boolean waitForRunResponse; private final Message runMessage; - public InternalStatementResultCursorFactory( Connection connection, Message runMessage, RunResponseHandler runHandler, BasicPullResponseHandler pullHandler, + public StatementResultCursorFactoryImpl( Connection connection, Message runMessage, RunResponseHandler runHandler, PullResponseHandler pullHandler, PullAllResponseHandler pullAllHandler, boolean waitForRunResponse ) { requireNonNull( connection ); @@ -59,19 +60,20 @@ public InternalStatementResultCursorFactory( Connection connection, Message runM } @Override - public CompletionStage asyncResult() + public CompletionStage asyncResult() { // only write and flush messages when async result is wanted. - connection.writeAndFlush( runMessage, runHandler, PullMessage.PULL_ALL, pullAllHandler ); + connection.write( runMessage, runHandler ); // queues the run message, will be flushed with pull message together + pullAllHandler.prePopulateRecords(); if ( waitForRunResponse ) { // wait for response of RUN before proceeding - return runHandler.runFuture().thenApply( ignore -> new AsyncStatementResultCursor( runHandler, pullAllHandler ) ); + return runHandler.runFuture().thenApply( ignore -> new AsyncStatementResultCursorImpl( runHandler, pullAllHandler ) ); } else { - return completedFuture( new AsyncStatementResultCursor( runHandler, pullAllHandler ) ); + return completedFuture( new AsyncStatementResultCursorImpl( runHandler, pullAllHandler ) ); } } @@ -85,6 +87,6 @@ public CompletionStage rxResult() private RxStatementResultCursor composeRxCursor( Throwable runError ) { - return new RxStatementResultCursor( runError, runHandler, pullHandler ); + return new RxStatementResultCursorImpl( runError, runHandler, pullHandler ); } } diff --git a/driver/src/main/java/org/neo4j/driver/internal/handlers/AbstractPullAllResponseHandler.java b/driver/src/main/java/org/neo4j/driver/internal/handlers/LegacyPullAllResponseHandler.java similarity index 92% rename from driver/src/main/java/org/neo4j/driver/internal/handlers/AbstractPullAllResponseHandler.java rename to driver/src/main/java/org/neo4j/driver/internal/handlers/LegacyPullAllResponseHandler.java index 7ec9c43431..00c04dfb05 100644 --- a/driver/src/main/java/org/neo4j/driver/internal/handlers/AbstractPullAllResponseHandler.java +++ b/driver/src/main/java/org/neo4j/driver/internal/handlers/LegacyPullAllResponseHandler.java @@ -27,14 +27,15 @@ import java.util.concurrent.CompletionStage; import java.util.function.Function; +import org.neo4j.driver.Record; +import org.neo4j.driver.Statement; +import org.neo4j.driver.Value; import org.neo4j.driver.internal.InternalRecord; +import org.neo4j.driver.internal.messaging.request.PullAllMessage; import org.neo4j.driver.internal.spi.Connection; import org.neo4j.driver.internal.util.Futures; import org.neo4j.driver.internal.util.Iterables; import org.neo4j.driver.internal.util.MetadataExtractor; -import org.neo4j.driver.Record; -import org.neo4j.driver.Statement; -import org.neo4j.driver.Value; import org.neo4j.driver.summary.ResultSummary; import static java.util.Collections.emptyMap; @@ -43,7 +44,10 @@ import static org.neo4j.driver.internal.util.Futures.completedWithNull; import static org.neo4j.driver.internal.util.Futures.failedFuture; -public abstract class AbstractPullAllResponseHandler implements PullAllResponseHandler +/** + * This is the Pull All response handler that handles pull all messages in Bolt v3 and previous protocol versions. + */ +public class LegacyPullAllResponseHandler implements PullAllResponseHandler { private static final Queue UNINITIALIZED_RECORDS = Iterables.emptyQueue(); @@ -54,6 +58,7 @@ public abstract class AbstractPullAllResponseHandler implements PullAllResponseH private final RunResponseHandler runResponseHandler; protected final MetadataExtractor metadataExtractor; protected final Connection connection; + private final PullResponseCompletionListener completionListener; // initialized lazily when first record arrives private Queue records = UNINITIALIZED_RECORDS; @@ -67,12 +72,14 @@ public abstract class AbstractPullAllResponseHandler implements PullAllResponseH private CompletableFuture recordFuture; private CompletableFuture failureFuture; - public AbstractPullAllResponseHandler( Statement statement, RunResponseHandler runResponseHandler, Connection connection, MetadataExtractor metadataExtractor ) + public LegacyPullAllResponseHandler( Statement statement, RunResponseHandler runResponseHandler, Connection connection, MetadataExtractor metadataExtractor, + PullResponseCompletionListener completionListener ) { this.statement = requireNonNull( statement ); this.runResponseHandler = requireNonNull( runResponseHandler ); this.metadataExtractor = requireNonNull( metadataExtractor ); this.connection = requireNonNull( connection ); + this.completionListener = requireNonNull( completionListener ); } @Override @@ -87,21 +94,19 @@ public synchronized void onSuccess( Map metadata ) finished = true; summary = extractResultSummary( metadata ); - afterSuccess( metadata ); + completionListener.afterSuccess( metadata ); completeRecordFuture( null ); completeFailureFuture( null ); } - protected abstract void afterSuccess( Map metadata ); - @Override public synchronized void onFailure( Throwable error ) { finished = true; summary = extractResultSummary( emptyMap() ); - afterFailure( error ); + completionListener.afterFailure( error ); boolean failedRecordFuture = failRecordFuture( error ); if ( failedRecordFuture ) @@ -120,8 +125,6 @@ public synchronized void onFailure( Throwable error ) } } - protected abstract void afterFailure( Throwable error ); - @Override public synchronized void onRecord( Value[] fields ) { @@ -177,6 +180,8 @@ public synchronized CompletionStage nextAsync() public synchronized CompletionStage summaryAsync() { + ignoreRecords = true; + records.clear(); return failureAsync().thenApply( error -> { if ( error != null ) @@ -187,13 +192,6 @@ public synchronized CompletionStage summaryAsync() } ); } - public synchronized CompletionStage consumeAsync() - { - ignoreRecords = true; - records.clear(); - return summaryAsync(); - } - public synchronized CompletionStage> listAsync( Function mapFunction ) { return failureAsync().thenApply( error -> @@ -206,6 +204,12 @@ public synchronized CompletionStage> listAsync( Function m } ); } + @Override + public void prePopulateRecords() + { + connection.writeAndFlush( PullAllMessage.PULL_ALL, this ); + } + public synchronized CompletionStage failureAsync() { 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 0dcbd3b384..05466cc5db 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 @@ -20,11 +20,11 @@ import java.util.List; import java.util.concurrent.CompletionStage; +import java.util.function.Function; -import org.neo4j.driver.internal.spi.ResponseHandler; import org.neo4j.driver.Record; +import org.neo4j.driver.internal.spi.ResponseHandler; import org.neo4j.driver.summary.ResultSummary; -import java.util.function.Function; public interface PullAllResponseHandler extends ResponseHandler { @@ -34,9 +34,9 @@ public interface PullAllResponseHandler extends ResponseHandler CompletionStage peekAsync(); - CompletionStage consumeAsync(); - CompletionStage> listAsync( Function mapFunction ); CompletionStage failureAsync(); + + void prePopulateRecords(); } diff --git a/driver/src/main/java/org/neo4j/driver/internal/handlers/PullHandlers.java b/driver/src/main/java/org/neo4j/driver/internal/handlers/PullHandlers.java index 934449bdde..fbcc22baec 100644 --- a/driver/src/main/java/org/neo4j/driver/internal/handlers/PullHandlers.java +++ b/driver/src/main/java/org/neo4j/driver/internal/handlers/PullHandlers.java @@ -21,43 +21,51 @@ import org.neo4j.driver.Statement; import org.neo4j.driver.internal.BookmarkHolder; import org.neo4j.driver.internal.async.ExplicitTransaction; +import org.neo4j.driver.internal.handlers.pulln.AutoPullResponseHandler; import org.neo4j.driver.internal.handlers.pulln.BasicPullResponseHandler; -import org.neo4j.driver.internal.handlers.pulln.SessionPullResponseHandler; -import org.neo4j.driver.internal.handlers.pulln.TransactionPullResponseHandler; +import org.neo4j.driver.internal.handlers.pulln.PullResponseHandler; import org.neo4j.driver.internal.messaging.v1.BoltProtocolV1; import org.neo4j.driver.internal.messaging.v3.BoltProtocolV3; import org.neo4j.driver.internal.spi.Connection; public class PullHandlers { - public static AbstractPullAllResponseHandler newBoltV1PullAllHandler( Statement statement, RunResponseHandler runHandler, + public static PullAllResponseHandler newBoltV1PullAllHandler( Statement statement, RunResponseHandler runHandler, Connection connection, ExplicitTransaction tx ) { - if ( tx != null ) - { - return new TransactionPullAllResponseHandler( statement, runHandler, connection, tx, BoltProtocolV1.METADATA_EXTRACTOR ); - } - return new SessionPullAllResponseHandler( statement, runHandler, connection, BookmarkHolder.NO_OP, BoltProtocolV1.METADATA_EXTRACTOR ); + PullResponseCompletionListener completionListener = createPullResponseCompletionListener( connection, BookmarkHolder.NO_OP, tx ); + + return new LegacyPullAllResponseHandler( statement, runHandler, connection, BoltProtocolV1.METADATA_EXTRACTOR, completionListener ); } - public static AbstractPullAllResponseHandler newBoltV3PullAllHandler( Statement statement, RunResponseHandler runHandler, Connection connection, + public static PullAllResponseHandler newBoltV3PullAllHandler( Statement statement, RunResponseHandler runHandler, Connection connection, BookmarkHolder bookmarkHolder, ExplicitTransaction tx ) { - if ( tx != null ) - { - return new TransactionPullAllResponseHandler( statement, runHandler, connection, tx, BoltProtocolV3.METADATA_EXTRACTOR ); - } - return new SessionPullAllResponseHandler( statement, runHandler, connection, bookmarkHolder, BoltProtocolV3.METADATA_EXTRACTOR ); + PullResponseCompletionListener completionListener = createPullResponseCompletionListener( connection, bookmarkHolder, tx ); + + return new LegacyPullAllResponseHandler( statement, runHandler, connection, BoltProtocolV3.METADATA_EXTRACTOR, completionListener ); + } + + public static PullAllResponseHandler newBoltV4AutoPullHandler( Statement statement, RunResponseHandler runHandler, Connection connection, + BookmarkHolder bookmarkHolder, ExplicitTransaction tx, long fetchSize ) + { + PullResponseCompletionListener completionListener = createPullResponseCompletionListener( connection, bookmarkHolder, tx ); + + return new AutoPullResponseHandler( statement, runHandler, connection, BoltProtocolV3.METADATA_EXTRACTOR, completionListener, fetchSize ); } - public static BasicPullResponseHandler newBoltV4PullHandler( Statement statement, RunResponseHandler runHandler, Connection connection, + + public static PullResponseHandler newBoltV4BasicPullHandler( Statement statement, RunResponseHandler runHandler, Connection connection, BookmarkHolder bookmarkHolder, ExplicitTransaction tx ) { - if ( tx != null ) - { - return new TransactionPullResponseHandler( statement, runHandler, connection, tx, BoltProtocolV3.METADATA_EXTRACTOR ); - } - return new SessionPullResponseHandler( statement, runHandler, connection, bookmarkHolder, BoltProtocolV3.METADATA_EXTRACTOR ); + PullResponseCompletionListener completionListener = createPullResponseCompletionListener( connection, bookmarkHolder, tx ); + + return new BasicPullResponseHandler( statement, runHandler, connection, BoltProtocolV3.METADATA_EXTRACTOR, completionListener ); } + private static PullResponseCompletionListener createPullResponseCompletionListener( Connection connection, BookmarkHolder bookmarkHolder, + ExplicitTransaction tx ) + { + return tx != null ? new TransactionPullResponseCompletionListener( tx ) : new SessionPullResponseCompletionListener( connection, bookmarkHolder ); + } } diff --git a/driver/src/main/java/org/neo4j/driver/internal/handlers/PullResponseCompletionListener.java b/driver/src/main/java/org/neo4j/driver/internal/handlers/PullResponseCompletionListener.java new file mode 100644 index 0000000000..1c3297c92b --- /dev/null +++ b/driver/src/main/java/org/neo4j/driver/internal/handlers/PullResponseCompletionListener.java @@ -0,0 +1,31 @@ +/* + * 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.handlers; + +import java.util.Map; + +import org.neo4j.driver.Value; + +public interface PullResponseCompletionListener +{ + void afterSuccess( Map metadata ); + + void afterFailure( Throwable error ); + +} diff --git a/driver/src/main/java/org/neo4j/driver/internal/handlers/SessionPullAllResponseHandler.java b/driver/src/main/java/org/neo4j/driver/internal/handlers/SessionPullResponseCompletionListener.java similarity index 69% rename from driver/src/main/java/org/neo4j/driver/internal/handlers/SessionPullAllResponseHandler.java rename to driver/src/main/java/org/neo4j/driver/internal/handlers/SessionPullResponseCompletionListener.java index 81a396a074..53a3cd5644 100644 --- a/driver/src/main/java/org/neo4j/driver/internal/handlers/SessionPullAllResponseHandler.java +++ b/driver/src/main/java/org/neo4j/driver/internal/handlers/SessionPullResponseCompletionListener.java @@ -20,34 +20,33 @@ import java.util.Map; +import org.neo4j.driver.Value; import org.neo4j.driver.internal.BookmarkHolder; import org.neo4j.driver.internal.spi.Connection; import org.neo4j.driver.internal.util.MetadataExtractor; -import org.neo4j.driver.Statement; -import org.neo4j.driver.Value; import static java.util.Objects.requireNonNull; -public class SessionPullAllResponseHandler extends AbstractPullAllResponseHandler +public class SessionPullResponseCompletionListener implements PullResponseCompletionListener { private final BookmarkHolder bookmarkHolder; + private final Connection connection; - public SessionPullAllResponseHandler( Statement statement, RunResponseHandler runResponseHandler, - Connection connection, BookmarkHolder bookmarkHolder, MetadataExtractor metadataExtractor ) + public SessionPullResponseCompletionListener( Connection connection, BookmarkHolder bookmarkHolder ) { - super( statement, runResponseHandler, connection, metadataExtractor ); + this.connection = requireNonNull( connection ); this.bookmarkHolder = requireNonNull( bookmarkHolder ); } @Override - protected void afterSuccess( Map metadata ) + public void afterSuccess( Map metadata ) { releaseConnection(); - bookmarkHolder.setBookmark( metadataExtractor.extractBookmarks( metadata ) ); + bookmarkHolder.setBookmark( MetadataExtractor.extractBookmarks( metadata ) ); } @Override - protected void afterFailure( Throwable error ) + public void afterFailure( Throwable error ) { releaseConnection(); } diff --git a/driver/src/main/java/org/neo4j/driver/internal/handlers/TransactionPullAllResponseHandler.java b/driver/src/main/java/org/neo4j/driver/internal/handlers/TransactionPullResponseCompletionListener.java similarity index 68% rename from driver/src/main/java/org/neo4j/driver/internal/handlers/TransactionPullAllResponseHandler.java rename to driver/src/main/java/org/neo4j/driver/internal/handlers/TransactionPullResponseCompletionListener.java index c9ee224487..73252c3614 100644 --- a/driver/src/main/java/org/neo4j/driver/internal/handlers/TransactionPullAllResponseHandler.java +++ b/driver/src/main/java/org/neo4j/driver/internal/handlers/TransactionPullResponseCompletionListener.java @@ -20,32 +20,27 @@ import java.util.Map; -import org.neo4j.driver.Statement; import org.neo4j.driver.Value; import org.neo4j.driver.internal.async.ExplicitTransaction; -import org.neo4j.driver.internal.spi.Connection; -import org.neo4j.driver.internal.util.MetadataExtractor; import static java.util.Objects.requireNonNull; -public class TransactionPullAllResponseHandler extends AbstractPullAllResponseHandler +public class TransactionPullResponseCompletionListener implements PullResponseCompletionListener { private final ExplicitTransaction tx; - public TransactionPullAllResponseHandler( Statement statement, RunResponseHandler runResponseHandler, - Connection connection, ExplicitTransaction tx, MetadataExtractor metadataExtractor ) + public TransactionPullResponseCompletionListener( ExplicitTransaction tx ) { - super( statement, runResponseHandler, connection, metadataExtractor ); this.tx = requireNonNull( tx ); } @Override - protected void afterSuccess( Map metadata ) + public void afterSuccess( Map metadata ) { } @Override - protected void afterFailure( Throwable error ) + public void afterFailure( Throwable error ) { // always mark transaction as terminated because every error is "acknowledged" with a RESET message // so database forgets about the transaction after the first error diff --git a/driver/src/main/java/org/neo4j/driver/internal/handlers/pulln/AbstractBasicPullResponseHandler.java b/driver/src/main/java/org/neo4j/driver/internal/handlers/pulln/AbstractBasicPullResponseHandler.java deleted file mode 100644 index 7ee433d2f1..0000000000 --- a/driver/src/main/java/org/neo4j/driver/internal/handlers/pulln/AbstractBasicPullResponseHandler.java +++ /dev/null @@ -1,279 +0,0 @@ -/* - * 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.handlers.pulln; - -import java.util.Map; -import java.util.function.BiConsumer; - -import org.neo4j.driver.internal.InternalRecord; -import org.neo4j.driver.internal.handlers.RunResponseHandler; -import org.neo4j.driver.internal.messaging.request.PullMessage; -import org.neo4j.driver.internal.spi.Connection; -import org.neo4j.driver.internal.util.MetadataExtractor; -import org.neo4j.driver.internal.value.BooleanValue; -import org.neo4j.driver.Record; -import org.neo4j.driver.Statement; -import org.neo4j.driver.Value; -import org.neo4j.driver.summary.ResultSummary; - -import static java.lang.String.format; -import static java.util.Collections.emptyMap; -import static java.util.Objects.requireNonNull; -import static org.neo4j.driver.internal.messaging.request.DiscardMessage.newDiscardAllMessage; - -/** - * In this class we have a hidden state machine. - * Here is how it looks like: - * | | DONE | FAILED | STREAMING | READY | CANCELED | - * |--------------------|------|--------|--------------------------------|--------------------|----------------| - * | request | X | X | toRequest++ ->STREAMING | PULL ->STREAMING | X | - * | cancel | X | X | ->CANCELED | DISCARD ->CANCELED | ->CANCELED | - * | onSuccess has_more | X | X | ->READY request if toRequest>0 | X | ->READY cancel | - * | onSuccess | X | X | summary ->DONE | X | summary ->DONE | - * | onRecord | X | X | yield record ->STREAMING | X | ->CANCELED | - * | onFailure | X | X | ->FAILED | X | ->FAILED | - * - * Currently the error state (marked with X on the table above) might not be enforced. - */ -public abstract class AbstractBasicPullResponseHandler implements BasicPullResponseHandler -{ - public static final BiConsumer DISCARD_RECORD_CONSUMER = ( record, throwable ) -> {/*do nothing*/}; - - private final Statement statement; - protected final RunResponseHandler runResponseHandler; - protected final MetadataExtractor metadataExtractor; - protected final Connection connection; - - private Status status = Status.READY; - private long toRequest; - private BiConsumer recordConsumer = null; - private BiConsumer summaryConsumer = null; - - protected abstract void afterSuccess( Map metadata ); - - protected abstract void afterFailure( Throwable error ); - - public AbstractBasicPullResponseHandler( Statement statement, RunResponseHandler runResponseHandler, Connection connection, MetadataExtractor metadataExtractor ) - { - this.statement = requireNonNull( statement ); - this.runResponseHandler = requireNonNull( runResponseHandler ); - this.metadataExtractor = requireNonNull( metadataExtractor ); - this.connection = requireNonNull( connection ); - } - - @Override - public synchronized void onSuccess( Map metadata ) - { - assertRecordAndSummaryConsumerInstalled(); - if ( metadata.getOrDefault( "has_more", BooleanValue.FALSE ).asBoolean() ) - { - handleSuccessWithHasMore(); - } - else - { - handleSuccessWithSummary( metadata ); - } - } - - @Override - public synchronized void onFailure( Throwable error ) - { - assertRecordAndSummaryConsumerInstalled(); - status = Status.FAILED; - afterFailure( error ); - - complete( extractResultSummary( emptyMap() ), error ); - } - - @Override - public synchronized void onRecord( Value[] fields ) - { - assertRecordAndSummaryConsumerInstalled(); - if ( isStreaming() ) - { - Record record = new InternalRecord( runResponseHandler.statementKeys(), fields ); - recordConsumer.accept( record, null ); - } - } - - @Override - public synchronized void request( long size ) - { - assertRecordAndSummaryConsumerInstalled(); - if ( isStreamingPaused() ) - { - connection.writeAndFlush( new PullMessage( size, runResponseHandler.statementId() ), this ); - status = Status.STREAMING; - } - else if ( isStreaming() ) - { - addToRequest( size ); - } - } - - @Override - public synchronized void cancel() - { - assertRecordAndSummaryConsumerInstalled(); - if ( isStreamingPaused() ) - { - // Reactive API does not provide a way to discard N. Only discard all. - connection.writeAndFlush( newDiscardAllMessage( runResponseHandler.statementId() ), this ); - status = Status.CANCELED; - } - else if ( isStreaming() ) - { - status = Status.CANCELED; - } - // no need to change status if it is already done - } - - @Override - public synchronized void installSummaryConsumer( BiConsumer summaryConsumer ) - { - if( this.summaryConsumer != null ) - { - throw new IllegalStateException( "Summary consumer already installed." ); - } - this.summaryConsumer = summaryConsumer; - } - - @Override - public synchronized void installRecordConsumer( BiConsumer recordConsumer ) - { - if( this.recordConsumer != null ) - { - throw new IllegalStateException( "Record consumer already installed." ); - } - this.recordConsumer = recordConsumer; - } - - private boolean isStreaming() - { - return status == Status.STREAMING; - } - - private boolean isStreamingPaused() - { - return status == Status.READY; - } - - private boolean isFinished() - { - return status == Status.DONE || status == Status.FAILED; - } - - private void handleSuccessWithSummary( Map metadata ) - { - status = Status.DONE; - afterSuccess( metadata ); - ResultSummary summary = extractResultSummary( metadata ); - - complete( summary, null ); - } - - private void handleSuccessWithHasMore() - { - if ( this.status == Status.CANCELED ) - { - this.status = Status.READY; // cancel request accepted. - cancel(); - } - else if ( this.status == Status.STREAMING ) - { - this.status = Status.READY; - if ( toRequest > 0 ) - { - request( toRequest ); - toRequest = 0; - } - // summary consumer use (null, null) to identify done handling of success with has_more - summaryConsumer.accept( null, null ); - } - } - - private ResultSummary extractResultSummary( Map metadata ) - { - long resultAvailableAfter = runResponseHandler.resultAvailableAfter(); - return metadataExtractor.extractSummary( statement, connection, resultAvailableAfter, metadata ); - } - - private void addToRequest( long toAdd ) - { - if ( toAdd <= 0 ) - { - throw new IllegalArgumentException( "Cannot request record amount that is less than or equal to 0. Request amount: " + toAdd ); - } - toRequest += toAdd; - if ( toRequest <= 0 ) // toAdd is already at least 1, we hit buffer overflow - { - toRequest = Long.MAX_VALUE; - } - } - - private void assertRecordAndSummaryConsumerInstalled() - { - if( isFinished() ) - { - // no need to check if we've finished. - return; - } - if( recordConsumer == null || summaryConsumer == null ) - { - throw new IllegalStateException( format("Access record stream without record consumer and/or summary consumer. " + - "Record consumer=%s, Summary consumer=%s", recordConsumer, summaryConsumer) ); - } - } - - private void complete( ResultSummary summary, Throwable error ) - { - // we first inform the summary consumer to ensure when streaming finished, summary is definitely available. - if ( recordConsumer == DISCARD_RECORD_CONSUMER ) - { - // we will report the error to summary if there is no record consumer - summaryConsumer.accept( summary, error ); - } - else - { - // we will not inform the error to summary as the error will be reported to record consumer - summaryConsumer.accept( summary, null ); - } - - // record consumer use (null, null) to identify the end of record stream - recordConsumer.accept( null, error ); - dispose(); - } - - private void dispose() - { - // release the reference to the consumers who hold the reference to subscribers which shall be released when subscription is completed. - this.recordConsumer = null; - this.summaryConsumer = null; - } - - protected Status status() - { - return this.status; - } - - protected void status( Status status ) - { - this.status = status; - } -} 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 new file mode 100644 index 0000000000..9aaea3009f --- /dev/null +++ b/driver/src/main/java/org/neo4j/driver/internal/handlers/pulln/AutoPullResponseHandler.java @@ -0,0 +1,297 @@ +/* + * 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.handlers.pulln; + +import java.util.ArrayDeque; +import java.util.ArrayList; +import java.util.List; +import java.util.Queue; +import java.util.concurrent.CompletableFuture; +import java.util.concurrent.CompletionStage; +import java.util.function.Function; + +import org.neo4j.driver.Record; +import org.neo4j.driver.Statement; +import org.neo4j.driver.internal.handlers.PullAllResponseHandler; +import org.neo4j.driver.internal.handlers.PullResponseCompletionListener; +import org.neo4j.driver.internal.handlers.RunResponseHandler; +import org.neo4j.driver.internal.spi.Connection; +import org.neo4j.driver.internal.util.Iterables; +import org.neo4j.driver.internal.util.MetadataExtractor; +import org.neo4j.driver.summary.ResultSummary; + +import static java.util.concurrent.CompletableFuture.completedFuture; +import static org.neo4j.driver.internal.handlers.pulln.FetchSizeUtil.UNLIMITED_FETCH_SIZE; +import static org.neo4j.driver.internal.util.Futures.completedWithNull; +import static org.neo4j.driver.internal.util.Futures.failedFuture; + +/** + * Built on top of {@link BasicPullResponseHandler} to be able to pull in batches. + * It is exposed as {@link PullAllResponseHandler} as it can automatically pull when running out of records locally. + */ +public class AutoPullResponseHandler extends BasicPullResponseHandler implements PullAllResponseHandler +{ + private static final Queue UNINITIALIZED_RECORDS = Iterables.emptyQueue(); + private final long fetchSize; + + // initialized lazily when first record arrives + private Queue records = UNINITIALIZED_RECORDS; + + private ResultSummary summary; + private Throwable failure; + + private CompletableFuture recordFuture; + private CompletableFuture summaryFuture; + + public AutoPullResponseHandler( Statement statement, RunResponseHandler runResponseHandler, Connection connection, MetadataExtractor metadataExtractor, + PullResponseCompletionListener completionListener, long fetchSize ) + { + super( statement, runResponseHandler, connection, metadataExtractor, completionListener ); + this.fetchSize = fetchSize; + installRecordAndSummaryConsumers(); + } + + private void installRecordAndSummaryConsumers() + { + installRecordConsumer( ( record, error ) -> { + if ( record != null ) + { + enqueueRecord( record ); + completeRecordFuture( record ); + } + // if ( error != null ) Handled by summary.error already + if ( record == null && error == null ) + { + // complete + completeRecordFuture( null ); + } + } ); + + installSummaryConsumer( ( summary, error ) -> { + if ( error != null ) + { + handleFailure( error ); + } + if ( summary != null ) + { + this.summary = summary; + completeSummaryFuture( summary ); + } + + if ( error == null && summary == null ) // has_more + { + request( fetchSize ); + } + } ); + } + + private void handleFailure( Throwable error ) + { + // error has not been propagated to the user, remember it + if ( !failRecordFuture( error ) && !failSummaryFuture( error ) ) + { + failure = error; + } + } + + public synchronized CompletionStage peekAsync() + { + Record record = records.peek(); + if ( record == null ) + { + if ( isDone() ) + { + return completedWithValueIfNoFailure( null ); + } + + if ( recordFuture == null ) + { + recordFuture = new CompletableFuture<>(); + } + return recordFuture; + } + else + { + return completedFuture( record ); + } + } + + public synchronized CompletionStage nextAsync() + { + return peekAsync().thenApply( ignore -> dequeueRecord() ); + } + + public synchronized CompletionStage summaryAsync() + { + if ( isDone() ) + { + records.clear(); + return completedWithValueIfNoFailure( summary ); + } + else + { + cancel(); + records.clear(); + if ( summaryFuture == null ) + { + summaryFuture = new CompletableFuture<>(); + } + + return summaryFuture; + } + } + + public synchronized CompletionStage> listAsync( Function mapFunction ) + { + return pullAllAsync().thenApply( summary -> recordsAsList( mapFunction ) ); + } + + @Override + public synchronized CompletionStage failureAsync() + { + return pullAllAsync().handle( ( ignore, error ) -> error ); + } + + @Override + public void prePopulateRecords() + { + request( fetchSize ); + } + + private synchronized CompletionStage pullAllAsync() + { + if ( isDone() ) + { + return completedWithValueIfNoFailure( summary ); + } + else + { + request( UNLIMITED_FETCH_SIZE ); + if ( summaryFuture == null ) + { + summaryFuture = new CompletableFuture<>(); + } + + return summaryFuture; + } + } + + private void enqueueRecord( Record record ) + { + if ( records == UNINITIALIZED_RECORDS ) + { + records = new ArrayDeque<>(); + } + + records.add( record ); + } + + private Record dequeueRecord() + { + return records.poll(); + } + + private List recordsAsList( Function mapFunction ) + { + if ( !isDone() ) + { + throw new IllegalStateException( "Can't get records as list because SUCCESS or FAILURE did not arrive" ); + } + + List result = new ArrayList<>( records.size() ); + while ( !records.isEmpty() ) + { + Record record = records.poll(); + result.add( mapFunction.apply( record ) ); + } + return result; + } + + private Throwable extractFailure() + { + if ( failure == null ) + { + throw new IllegalStateException( "Can't extract failure because it does not exist" ); + } + + Throwable error = failure; + failure = null; // propagate failure only once + return error; + } + + private void completeRecordFuture( Record record ) + { + if ( recordFuture != null ) + { + CompletableFuture future = recordFuture; + recordFuture = null; + future.complete( record ); + } + } + + private void completeSummaryFuture( ResultSummary summary ) + { + if ( summaryFuture != null ) + { + CompletableFuture future = summaryFuture; + summaryFuture = null; + future.complete( summary ); + } + } + + private boolean failRecordFuture( Throwable error ) + { + if ( recordFuture != null ) + { + CompletableFuture future = recordFuture; + recordFuture = null; + future.completeExceptionally( error ); + return true; + } + return false; + } + + private boolean failSummaryFuture( Throwable error ) + { + if ( summaryFuture != null ) + { + CompletableFuture future = summaryFuture; + summaryFuture = null; + future.completeExceptionally( error ); + return true; + } + return false; + } + + private CompletionStage completedWithValueIfNoFailure( T value ) + { + if ( failure != null ) + { + return failedFuture( extractFailure() ); + } + else if ( value == null ) + { + return completedWithNull(); + } + else + { + return completedFuture( value ); + } + } +} diff --git a/driver/src/main/java/org/neo4j/driver/internal/handlers/pulln/BasicPullResponseHandler.java b/driver/src/main/java/org/neo4j/driver/internal/handlers/pulln/BasicPullResponseHandler.java index f0bc3b700b..29eaee281a 100644 --- a/driver/src/main/java/org/neo4j/driver/internal/handlers/pulln/BasicPullResponseHandler.java +++ b/driver/src/main/java/org/neo4j/driver/internal/handlers/pulln/BasicPullResponseHandler.java @@ -18,38 +18,262 @@ */ package org.neo4j.driver.internal.handlers.pulln; -import org.reactivestreams.Subscription; - +import java.util.Map; import java.util.function.BiConsumer; -import org.neo4j.driver.internal.spi.ResponseHandler; import org.neo4j.driver.Record; +import org.neo4j.driver.Statement; +import org.neo4j.driver.Value; +import org.neo4j.driver.internal.InternalRecord; +import org.neo4j.driver.internal.handlers.PullResponseCompletionListener; +import org.neo4j.driver.internal.handlers.RunResponseHandler; +import org.neo4j.driver.internal.messaging.request.PullMessage; +import org.neo4j.driver.internal.spi.Connection; +import org.neo4j.driver.internal.util.MetadataExtractor; +import org.neo4j.driver.internal.value.BooleanValue; import org.neo4j.driver.summary.ResultSummary; -public interface BasicPullResponseHandler extends ResponseHandler, Subscription +import static java.lang.String.format; +import static java.util.Collections.emptyMap; +import static java.util.Objects.requireNonNull; +import static org.neo4j.driver.internal.handlers.pulln.FetchSizeUtil.UNLIMITED_FETCH_SIZE; +import static org.neo4j.driver.internal.messaging.request.DiscardMessage.newDiscardAllMessage; + +/** + * In this class we have a hidden state machine. + * Here is how it looks like: + * | | DONE | FAILED | STREAMING | READY | CANCELED | + * |--------------------|------|--------|--------------------------------|--------------------|----------------| + * | request | X | X | toRequest++ ->STREAMING | PULL ->STREAMING | X | + * | cancel | X | X | ->CANCELED | DISCARD ->CANCELED | ->CANCELED | + * | onSuccess has_more | X | X | ->READY request if toRequest>0 | X | ->READY cancel | + * | onSuccess | X | X | summary ->DONE | X | summary ->DONE | + * | onRecord | X | X | yield record ->STREAMING | X | ->CANCELED | + * | onFailure | X | X | ->FAILED | X | ->FAILED | + * + * Currently the error state (marked with X on the table above) might not be enforced. + */ +public class BasicPullResponseHandler implements PullResponseHandler { - /** - * Register a record consumer for each record received. - * STREAMING shall not be started before this consumer is registered. - * A null record with no error indicates the end of streaming. - * @param recordConsumer register a record consumer to be notified for each record received. - */ - void installRecordConsumer( BiConsumer recordConsumer ); - - /** - * Register a summary consumer to be notified when a summary is received. - * STREAMING shall not be started before this consumer is registered. - * A null summary with no error indicates a SUCCESS message with has_more=true has arrived. - * @param summaryConsumer register a summary consumer - */ - void installSummaryConsumer( BiConsumer summaryConsumer ); - - enum Status - { - DONE, // successfully completed - FAILED, // failed - CANCELED, // canceled - STREAMING, // streaming records - READY // steaming is paused. ready to accept request or cancel commands from user + private final Statement statement; + protected final RunResponseHandler runResponseHandler; + protected final MetadataExtractor metadataExtractor; + protected final Connection connection; + private final PullResponseCompletionListener completionListener; + + private Status status = Status.READY; + private long toRequest; + private BiConsumer recordConsumer = null; + private BiConsumer summaryConsumer = null; + + public BasicPullResponseHandler( Statement statement, RunResponseHandler runResponseHandler, Connection connection, MetadataExtractor metadataExtractor, + PullResponseCompletionListener completionListener ) + { + this.statement = requireNonNull( statement ); + this.runResponseHandler = requireNonNull( runResponseHandler ); + this.metadataExtractor = requireNonNull( metadataExtractor ); + this.connection = requireNonNull( connection ); + this.completionListener = requireNonNull( completionListener ); + } + + @Override + public synchronized void onSuccess( Map metadata ) + { + assertRecordAndSummaryConsumerInstalled(); + if ( metadata.getOrDefault( "has_more", BooleanValue.FALSE ).asBoolean() ) + { + handleSuccessWithHasMore(); + } + else + { + handleSuccessWithSummary( metadata ); + } + } + + @Override + public synchronized void onFailure( Throwable error ) + { + assertRecordAndSummaryConsumerInstalled(); + status = Status.FAILED; + completionListener.afterFailure( error ); + + complete( extractResultSummary( emptyMap() ), error ); + } + + @Override + public synchronized void onRecord( Value[] fields ) + { + assertRecordAndSummaryConsumerInstalled(); + if ( isStreaming() ) + { + Record record = new InternalRecord( runResponseHandler.statementKeys(), fields ); + recordConsumer.accept( record, null ); + } + } + + @Override + public synchronized void request( long size ) + { + assertRecordAndSummaryConsumerInstalled(); + if ( isStreamingPaused() ) + { + status = Status.STREAMING; + connection.writeAndFlush( new PullMessage( size, runResponseHandler.statementId() ), this ); + } + else if ( isStreaming() ) + { + addToRequest( size ); + } + } + + @Override + public synchronized void cancel() + { + assertRecordAndSummaryConsumerInstalled(); + if ( isStreamingPaused() ) + { + status = Status.CANCELED; + // Reactive API does not provide a way to discard N. Only discard all. + connection.writeAndFlush( newDiscardAllMessage( runResponseHandler.statementId() ), this ); + } + else if ( isStreaming() ) + { + status = Status.CANCELED; + } + // no need to change status if it is already done + } + + @Override + public synchronized void installSummaryConsumer( BiConsumer summaryConsumer ) + { + if( this.summaryConsumer != null ) + { + throw new IllegalStateException( "Summary consumer already installed." ); + } + this.summaryConsumer = summaryConsumer; + } + + @Override + public synchronized void installRecordConsumer( BiConsumer recordConsumer ) + { + if( this.recordConsumer != null ) + { + throw new IllegalStateException( "Record consumer already installed." ); + } + this.recordConsumer = recordConsumer; + } + + private boolean isStreaming() + { + return status == Status.STREAMING; + } + + private boolean isStreamingPaused() + { + return status == Status.READY; + } + + protected boolean isDone() + { + return status == Status.SUCCEEDED || status == Status.FAILED; + } + + private void handleSuccessWithSummary( Map metadata ) + { + status = Status.SUCCEEDED; + completionListener.afterSuccess( metadata ); + ResultSummary summary = extractResultSummary( metadata ); + + complete( summary, null ); + } + + private void handleSuccessWithHasMore() + { + if ( this.status == Status.CANCELED ) + { + this.status = Status.READY; // cancel request accepted. + cancel(); + } + else if ( this.status == Status.STREAMING ) + { + this.status = Status.READY; + if ( toRequest > 0 || toRequest == UNLIMITED_FETCH_SIZE ) + { + request( toRequest ); + toRequest = 0; + } + // summary consumer use (null, null) to identify done handling of success with has_more + summaryConsumer.accept( null, null ); + } + } + + private ResultSummary extractResultSummary( Map metadata ) + { + long resultAvailableAfter = runResponseHandler.resultAvailableAfter(); + return metadataExtractor.extractSummary( statement, connection, resultAvailableAfter, metadata ); + } + + private void addToRequest( long toAdd ) + { + if ( toRequest == UNLIMITED_FETCH_SIZE ) + { + return; + } + if ( toAdd == UNLIMITED_FETCH_SIZE ) + { + // pull all + toRequest = UNLIMITED_FETCH_SIZE; + return; + } + + if ( toAdd <= 0 ) + { + throw new IllegalArgumentException( "Cannot request record amount that is less than or equal to 0. Request amount: " + toAdd ); + } + toRequest += toAdd; + if ( toRequest <= 0 ) // toAdd is already at least 1, we hit buffer overflow + { + toRequest = Long.MAX_VALUE; + } + } + + private void assertRecordAndSummaryConsumerInstalled() + { + if( isDone() ) + { + // no need to check if we've finished. + return; + } + if( recordConsumer == null || summaryConsumer == null ) + { + throw new IllegalStateException( format("Access record stream without record consumer and/or summary consumer. " + + "Record consumer=%s, Summary consumer=%s", recordConsumer, summaryConsumer) ); + } + } + + private void complete( ResultSummary summary, Throwable error ) + { + // we first inform the summary consumer to ensure when streaming finished, summary is definitely available. + summaryConsumer.accept( summary, error ); + // record consumer use (null, null) to identify the end of record stream + recordConsumer.accept( null, error ); + dispose(); + } + + private void dispose() + { + // release the reference to the consumers who hold the reference to subscribers which shall be released when subscription is completed. + this.recordConsumer = null; + this.summaryConsumer = null; + } + + protected Status status() + { + return this.status; + } + + protected void status( Status status ) + { + this.status = status; } } diff --git a/driver/src/main/java/org/neo4j/driver/internal/handlers/pulln/FetchSizeUtil.java b/driver/src/main/java/org/neo4j/driver/internal/handlers/pulln/FetchSizeUtil.java new file mode 100644 index 0000000000..c8d669cc77 --- /dev/null +++ b/driver/src/main/java/org/neo4j/driver/internal/handlers/pulln/FetchSizeUtil.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.internal.handlers.pulln; + +public class FetchSizeUtil +{ + public static final long UNLIMITED_FETCH_SIZE = -1; + public static final long DEFAULT_FETCH_SIZE = 1000; + + public static long assertValidFetchSize( long size ) + { + if ( size <= 0 && size != UNLIMITED_FETCH_SIZE ) + { + throw new IllegalArgumentException( String.format( "The record fetch size may not be 0 or negative. Illegal record fetch size: %s.", size ) ); + } + return size; + } +} diff --git a/driver/src/main/java/org/neo4j/driver/internal/handlers/pulln/PullResponseHandler.java b/driver/src/main/java/org/neo4j/driver/internal/handlers/pulln/PullResponseHandler.java new file mode 100644 index 0000000000..5ca4130ab8 --- /dev/null +++ b/driver/src/main/java/org/neo4j/driver/internal/handlers/pulln/PullResponseHandler.java @@ -0,0 +1,55 @@ +/* + * 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.handlers.pulln; + +import org.reactivestreams.Subscription; + +import java.util.function.BiConsumer; + +import org.neo4j.driver.internal.spi.ResponseHandler; +import org.neo4j.driver.Record; +import org.neo4j.driver.summary.ResultSummary; + +public interface PullResponseHandler extends ResponseHandler, Subscription +{ + /** + * Register a record consumer for each record received. + * STREAMING shall not be started before this consumer is registered. + * A null record with no error indicates the end of streaming. + * @param recordConsumer register a record consumer to be notified for each record received. + */ + void installRecordConsumer( BiConsumer recordConsumer ); + + /** + * Register a summary consumer to be notified when a summary is received. + * STREAMING shall not be started before this consumer is registered. + * A null summary with no error indicates a SUCCESS message with has_more=true has arrived. + * @param summaryConsumer register a summary consumer + */ + void installSummaryConsumer( BiConsumer summaryConsumer ); + + enum Status + { + SUCCEEDED, // successfully completed + FAILED, // failed + CANCELED, // canceled + STREAMING, // streaming records + READY // steaming is paused. ready to accept request or cancel commands from user + } +} diff --git a/driver/src/main/java/org/neo4j/driver/internal/handlers/pulln/SessionPullResponseHandler.java b/driver/src/main/java/org/neo4j/driver/internal/handlers/pulln/SessionPullResponseHandler.java deleted file mode 100644 index 5f2cebed4f..0000000000 --- a/driver/src/main/java/org/neo4j/driver/internal/handlers/pulln/SessionPullResponseHandler.java +++ /dev/null @@ -1,60 +0,0 @@ -/* - * 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.handlers.pulln; - -import java.util.Map; - -import org.neo4j.driver.internal.BookmarkHolder; -import org.neo4j.driver.internal.handlers.RunResponseHandler; -import org.neo4j.driver.internal.spi.Connection; -import org.neo4j.driver.internal.util.MetadataExtractor; -import org.neo4j.driver.Statement; -import org.neo4j.driver.Value; - -import static java.util.Objects.requireNonNull; - -public class SessionPullResponseHandler extends AbstractBasicPullResponseHandler -{ - private final BookmarkHolder bookmarkHolder; - - public SessionPullResponseHandler( Statement statement, RunResponseHandler runResponseHandler, - Connection connection, BookmarkHolder bookmarkHolder, MetadataExtractor metadataExtractor ) - { - super( statement, runResponseHandler, connection, metadataExtractor ); - this.bookmarkHolder = requireNonNull( bookmarkHolder ); - } - - @Override - protected void afterSuccess( Map metadata ) - { - releaseConnection(); - bookmarkHolder.setBookmark( metadataExtractor.extractBookmarks( metadata ) ); - } - - @Override - protected void afterFailure( Throwable error ) - { - releaseConnection(); - } - - private void releaseConnection() - { - connection.release(); // release in background - } -} diff --git a/driver/src/main/java/org/neo4j/driver/internal/handlers/pulln/TransactionPullResponseHandler.java b/driver/src/main/java/org/neo4j/driver/internal/handlers/pulln/TransactionPullResponseHandler.java deleted file mode 100644 index 5944d850cb..0000000000 --- a/driver/src/main/java/org/neo4j/driver/internal/handlers/pulln/TransactionPullResponseHandler.java +++ /dev/null @@ -1,56 +0,0 @@ -/* - * 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.handlers.pulln; - -import java.util.Map; - -import org.neo4j.driver.Statement; -import org.neo4j.driver.Value; -import org.neo4j.driver.internal.async.ExplicitTransaction; -import org.neo4j.driver.internal.handlers.RunResponseHandler; -import org.neo4j.driver.internal.spi.Connection; -import org.neo4j.driver.internal.util.MetadataExtractor; - -import static java.util.Objects.requireNonNull; - -public class TransactionPullResponseHandler extends AbstractBasicPullResponseHandler -{ - private final ExplicitTransaction tx; - - public TransactionPullResponseHandler( Statement statement, RunResponseHandler runResponseHandler, - Connection connection, ExplicitTransaction tx, MetadataExtractor metadataExtractor ) - { - super( statement, runResponseHandler, connection, metadataExtractor ); - this.tx = requireNonNull( tx ); - } - - @Override - protected void afterSuccess( Map metadata ) - { - } - - @Override - protected void afterFailure( Throwable error ) - { - // always mark transaction as terminated because every error is "acknowledged" with a RESET message - // so database forgets about the transaction after the first error - // such transaction should not attempt to commit and can be considered as rolled back - tx.markTerminated(); - } -} diff --git a/driver/src/main/java/org/neo4j/driver/internal/messaging/BoltProtocol.java b/driver/src/main/java/org/neo4j/driver/internal/messaging/BoltProtocol.java index 5255afc90b..13363fafd5 100644 --- a/driver/src/main/java/org/neo4j/driver/internal/messaging/BoltProtocol.java +++ b/driver/src/main/java/org/neo4j/driver/internal/messaging/BoltProtocol.java @@ -102,10 +102,11 @@ public interface BoltProtocol * @param waitForRunResponse {@code true} for async query execution and {@code false} for blocking query * execution. Makes returned cursor stage be chained after the RUN response arrives. Needed to have statement * keys populated. + * @param fetchSize the record fetch size for PULL message. * @return stage with cursor. */ - StatementResultCursorFactory runInAutoCommitTransaction( Connection connection, Statement statement, - BookmarkHolder bookmarkHolder, TransactionConfig config, boolean waitForRunResponse ); + StatementResultCursorFactory runInAutoCommitTransaction( Connection connection, Statement statement, BookmarkHolder bookmarkHolder, + TransactionConfig config, boolean waitForRunResponse, long fetchSize ); /** * Execute the given statement in a running explicit transaction, i.e. {@link Transaction#run(Statement)}. @@ -116,10 +117,11 @@ StatementResultCursorFactory runInAutoCommitTransaction( Connection connection, * @param waitForRunResponse {@code true} for async query execution and {@code false} for blocking query * execution. Makes returned cursor stage be chained after the RUN response arrives. Needed to have statement * keys populated. + * @param fetchSize the record fetch size for PULL message. * @return stage with cursor. */ - StatementResultCursorFactory runInExplicitTransaction( Connection connection, Statement statement, ExplicitTransaction tx, - boolean waitForRunResponse ); + StatementResultCursorFactory runInExplicitTransaction( Connection connection, Statement statement, ExplicitTransaction tx, boolean waitForRunResponse, + long fetchSize ); /** * Returns the protocol version. It can be used for version specific error messages. diff --git a/driver/src/main/java/org/neo4j/driver/internal/messaging/v1/BoltProtocolV1.java b/driver/src/main/java/org/neo4j/driver/internal/messaging/v1/BoltProtocolV1.java index 4be49d145c..8fedd49812 100644 --- a/driver/src/main/java/org/neo4j/driver/internal/messaging/v1/BoltProtocolV1.java +++ b/driver/src/main/java/org/neo4j/driver/internal/messaging/v1/BoltProtocolV1.java @@ -34,13 +34,13 @@ import org.neo4j.driver.internal.DatabaseName; import org.neo4j.driver.internal.InternalBookmark; import org.neo4j.driver.internal.async.ExplicitTransaction; -import org.neo4j.driver.internal.cursor.AsyncResultCursorOnlyFactory; +import org.neo4j.driver.internal.cursor.AsyncStatementResultCursorOnlyFactory; import org.neo4j.driver.internal.cursor.StatementResultCursorFactory; -import org.neo4j.driver.internal.handlers.AbstractPullAllResponseHandler; import org.neo4j.driver.internal.handlers.BeginTxResponseHandler; import org.neo4j.driver.internal.handlers.CommitTxResponseHandler; import org.neo4j.driver.internal.handlers.InitResponseHandler; import org.neo4j.driver.internal.handlers.NoOpResponseHandler; +import org.neo4j.driver.internal.handlers.PullAllResponseHandler; import org.neo4j.driver.internal.handlers.PullHandlers; import org.neo4j.driver.internal.handlers.RollbackTxResponseHandler; import org.neo4j.driver.internal.handlers.RunResponseHandler; @@ -159,8 +159,8 @@ public CompletionStage rollbackTransaction( Connection connection ) } @Override - public StatementResultCursorFactory runInAutoCommitTransaction( Connection connection, Statement statement, - BookmarkHolder bookmarkHolder, TransactionConfig config, boolean waitForRunResponse ) + public StatementResultCursorFactory runInAutoCommitTransaction( Connection connection, Statement statement, BookmarkHolder bookmarkHolder, + TransactionConfig config, boolean waitForRunResponse, long ignored ) { // bookmarks are ignored for auto-commit transactions in this version of the protocol verifyBeforeTransaction( config, connection.databaseName() ); @@ -169,7 +169,7 @@ public StatementResultCursorFactory runInAutoCommitTransaction( Connection conne @Override public StatementResultCursorFactory runInExplicitTransaction( Connection connection, Statement statement, ExplicitTransaction tx, - boolean waitForRunResponse ) + boolean waitForRunResponse, long ignored ) { return buildResultCursorFactory( connection, statement, tx, waitForRunResponse ); } @@ -188,9 +188,9 @@ private static StatementResultCursorFactory buildResultCursorFactory( Connection RunMessage runMessage = new RunMessage( query, params ); RunResponseHandler runHandler = new RunResponseHandler( METADATA_EXTRACTOR ); - AbstractPullAllResponseHandler pullAllHandler = PullHandlers.newBoltV1PullAllHandler( statement, runHandler, connection, tx ); + PullAllResponseHandler pullAllHandler = PullHandlers.newBoltV1PullAllHandler( statement, runHandler, connection, tx ); - return new AsyncResultCursorOnlyFactory( connection, runMessage, runHandler, pullAllHandler, waitForRunResponse ); + return new AsyncStatementResultCursorOnlyFactory( connection, runMessage, runHandler, pullAllHandler, waitForRunResponse ); } private void verifyBeforeTransaction( TransactionConfig config, DatabaseName databaseName ) diff --git a/driver/src/main/java/org/neo4j/driver/internal/messaging/v3/BoltProtocolV3.java b/driver/src/main/java/org/neo4j/driver/internal/messaging/v3/BoltProtocolV3.java index eb3b7d6039..d663bbbb4c 100644 --- a/driver/src/main/java/org/neo4j/driver/internal/messaging/v3/BoltProtocolV3.java +++ b/driver/src/main/java/org/neo4j/driver/internal/messaging/v3/BoltProtocolV3.java @@ -32,13 +32,13 @@ import org.neo4j.driver.internal.DatabaseName; import org.neo4j.driver.internal.InternalBookmark; import org.neo4j.driver.internal.async.ExplicitTransaction; -import org.neo4j.driver.internal.cursor.AsyncResultCursorOnlyFactory; +import org.neo4j.driver.internal.cursor.AsyncStatementResultCursorOnlyFactory; import org.neo4j.driver.internal.cursor.StatementResultCursorFactory; -import org.neo4j.driver.internal.handlers.AbstractPullAllResponseHandler; import org.neo4j.driver.internal.handlers.BeginTxResponseHandler; import org.neo4j.driver.internal.handlers.CommitTxResponseHandler; import org.neo4j.driver.internal.handlers.HelloResponseHandler; import org.neo4j.driver.internal.handlers.NoOpResponseHandler; +import org.neo4j.driver.internal.handlers.PullAllResponseHandler; import org.neo4j.driver.internal.handlers.RollbackTxResponseHandler; import org.neo4j.driver.internal.handlers.RunResponseHandler; import org.neo4j.driver.internal.messaging.BoltProtocol; @@ -137,30 +137,30 @@ public CompletionStage rollbackTransaction( Connection connection ) } @Override - public StatementResultCursorFactory runInAutoCommitTransaction( Connection connection, Statement statement, - BookmarkHolder bookmarkHolder, TransactionConfig config, boolean waitForRunResponse ) + public StatementResultCursorFactory runInAutoCommitTransaction( Connection connection, Statement statement, BookmarkHolder bookmarkHolder, + TransactionConfig config, boolean waitForRunResponse, long fetchSize ) { verifyDatabaseNameBeforeTransaction( connection.databaseName() ); RunWithMetadataMessage runMessage = autoCommitTxRunMessage( statement, config, connection.databaseName(), connection.mode(), bookmarkHolder.getBookmark() ); - return buildResultCursorFactory( connection, statement, bookmarkHolder, null, runMessage, waitForRunResponse ); + return buildResultCursorFactory( connection, statement, bookmarkHolder, null, runMessage, waitForRunResponse, fetchSize ); } @Override public StatementResultCursorFactory runInExplicitTransaction( Connection connection, Statement statement, ExplicitTransaction tx, - boolean waitForRunResponse ) + boolean waitForRunResponse, long fetchSize ) { RunWithMetadataMessage runMessage = explicitTxRunMessage( statement ); - return buildResultCursorFactory( connection, statement, BookmarkHolder.NO_OP, tx, runMessage, waitForRunResponse ); + return buildResultCursorFactory( connection, statement, BookmarkHolder.NO_OP, tx, runMessage, waitForRunResponse, fetchSize ); } protected StatementResultCursorFactory buildResultCursorFactory( Connection connection, Statement statement, BookmarkHolder bookmarkHolder, - ExplicitTransaction tx, RunWithMetadataMessage runMessage, boolean waitForRunResponse ) + ExplicitTransaction tx, RunWithMetadataMessage runMessage, boolean waitForRunResponse, long ignored ) { RunResponseHandler runHandler = new RunResponseHandler( METADATA_EXTRACTOR ); - AbstractPullAllResponseHandler pullHandler = newBoltV3PullAllHandler( statement, runHandler, connection, bookmarkHolder, tx ); + PullAllResponseHandler pullHandler = newBoltV3PullAllHandler( statement, runHandler, connection, bookmarkHolder, tx ); - return new AsyncResultCursorOnlyFactory( connection, runMessage, runHandler, pullHandler, waitForRunResponse ); + return new AsyncStatementResultCursorOnlyFactory( connection, runMessage, runHandler, pullHandler, waitForRunResponse ); } protected void verifyDatabaseNameBeforeTransaction( DatabaseName databaseName ) diff --git a/driver/src/main/java/org/neo4j/driver/internal/messaging/v4/BoltProtocolV4.java b/driver/src/main/java/org/neo4j/driver/internal/messaging/v4/BoltProtocolV4.java index 21d5927fa0..b00134c460 100644 --- a/driver/src/main/java/org/neo4j/driver/internal/messaging/v4/BoltProtocolV4.java +++ b/driver/src/main/java/org/neo4j/driver/internal/messaging/v4/BoltProtocolV4.java @@ -22,19 +22,19 @@ import org.neo4j.driver.internal.BookmarkHolder; import org.neo4j.driver.internal.DatabaseName; import org.neo4j.driver.internal.async.ExplicitTransaction; -import org.neo4j.driver.internal.cursor.InternalStatementResultCursorFactory; +import org.neo4j.driver.internal.cursor.StatementResultCursorFactoryImpl; import org.neo4j.driver.internal.cursor.StatementResultCursorFactory; -import org.neo4j.driver.internal.handlers.AbstractPullAllResponseHandler; +import org.neo4j.driver.internal.handlers.PullAllResponseHandler; import org.neo4j.driver.internal.handlers.RunResponseHandler; -import org.neo4j.driver.internal.handlers.pulln.BasicPullResponseHandler; +import org.neo4j.driver.internal.handlers.pulln.PullResponseHandler; import org.neo4j.driver.internal.messaging.BoltProtocol; import org.neo4j.driver.internal.messaging.MessageFormat; import org.neo4j.driver.internal.messaging.request.RunWithMetadataMessage; import org.neo4j.driver.internal.messaging.v3.BoltProtocolV3; import org.neo4j.driver.internal.spi.Connection; -import static org.neo4j.driver.internal.handlers.PullHandlers.newBoltV3PullAllHandler; -import static org.neo4j.driver.internal.handlers.PullHandlers.newBoltV4PullHandler; +import static org.neo4j.driver.internal.handlers.PullHandlers.newBoltV4AutoPullHandler; +import static org.neo4j.driver.internal.handlers.PullHandlers.newBoltV4BasicPullHandler; public class BoltProtocolV4 extends BoltProtocolV3 { @@ -49,14 +49,14 @@ public MessageFormat createMessageFormat() @Override protected StatementResultCursorFactory buildResultCursorFactory( Connection connection, Statement statement, BookmarkHolder bookmarkHolder, - ExplicitTransaction tx, RunWithMetadataMessage runMessage, boolean waitForRunResponse ) + ExplicitTransaction tx, RunWithMetadataMessage runMessage, boolean waitForRunResponse, long fetchSize ) { RunResponseHandler runHandler = new RunResponseHandler( METADATA_EXTRACTOR ); - AbstractPullAllResponseHandler pullAllHandler = newBoltV3PullAllHandler( statement, runHandler, connection, bookmarkHolder, tx ); - BasicPullResponseHandler pullHandler = newBoltV4PullHandler( statement, runHandler, connection, bookmarkHolder, tx ); + PullAllResponseHandler pullAllHandler = newBoltV4AutoPullHandler( statement, runHandler, connection, bookmarkHolder, tx, fetchSize ); + PullResponseHandler pullHandler = newBoltV4BasicPullHandler( statement, runHandler, connection, bookmarkHolder, tx ); - return new InternalStatementResultCursorFactory( connection, runMessage, runHandler, pullHandler, pullAllHandler, waitForRunResponse ); + return new StatementResultCursorFactoryImpl( connection, runMessage, runHandler, pullHandler, pullAllHandler, waitForRunResponse ); } @Override 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 1cbe3e0f42..381c0f18cd 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" ).consume().server().version() ); + String versionString = session.readTransaction( tx -> tx.run( "RETURN 1" ).summary().server().version() ); return version( versionString ); } } diff --git a/driver/src/test/java/org/neo4j/driver/ConfigTest.java b/driver/src/test/java/org/neo4j/driver/ConfigTest.java index 1847ab7008..395c0d5429 100644 --- a/driver/src/test/java/org/neo4j/driver/ConfigTest.java +++ b/driver/src/test/java/org/neo4j/driver/ConfigTest.java @@ -19,18 +19,23 @@ package org.neo4j.driver; import org.junit.jupiter.api.Test; +import org.junit.jupiter.params.ParameterizedTest; +import org.junit.jupiter.params.provider.ValueSource; import java.io.File; import java.util.concurrent.TimeUnit; import org.neo4j.driver.net.ServerAddressResolver; +import static org.hamcrest.CoreMatchers.equalTo; +import static org.junit.Assert.assertThat; import static org.junit.jupiter.api.Assertions.assertEquals; import static org.junit.jupiter.api.Assertions.assertFalse; import static org.junit.jupiter.api.Assertions.assertSame; import static org.junit.jupiter.api.Assertions.assertThrows; import static org.junit.jupiter.api.Assertions.assertTrue; import static org.mockito.Mockito.mock; +import static org.neo4j.driver.internal.handlers.pulln.FetchSizeUtil.DEFAULT_FETCH_SIZE; class ConfigTest { @@ -273,4 +278,26 @@ void shouldNotAllowNullResolver() { assertThrows( NullPointerException.class, () -> Config.builder().withResolver( null ) ); } + + @Test + void shouldDefaultToDefaultFetchSize() throws Throwable + { + Config config = Config.defaultConfig(); + assertEquals( DEFAULT_FETCH_SIZE, config.fetchSize() ); + } + + @ParameterizedTest + @ValueSource( longs = {100, 1, 1000, Long.MAX_VALUE, -1} ) + void shouldChangeFetchSize( long value ) throws Throwable + { + Config config = Config.builder().withFetchSize( value ).build(); + assertThat( config.fetchSize(), equalTo( value ) ); + } + + @ParameterizedTest + @ValueSource( longs = {0, -100, -2} ) + void shouldErrorWithIllegalFetchSize( long value ) throws Throwable + { + assertThrows( IllegalArgumentException.class, () -> Config.builder().withFetchSize( value ).build() ); + } } diff --git a/driver/src/test/java/org/neo4j/driver/ParametersTest.java b/driver/src/test/java/org/neo4j/driver/ParametersTest.java index 2f64b0d667..a74e4d3400 100644 --- a/driver/src/test/java/org/neo4j/driver/ParametersTest.java +++ b/driver/src/test/java/org/neo4j/driver/ParametersTest.java @@ -42,6 +42,7 @@ import static org.mockito.Mockito.mock; import static org.neo4j.driver.Values.parameters; import static org.neo4j.driver.internal.DatabaseNameUtil.defaultDatabase; +import static org.neo4j.driver.internal.handlers.pulln.FetchSizeUtil.UNLIMITED_FETCH_SIZE; import static org.neo4j.driver.internal.logging.DevNullLogging.DEV_NULL_LOGGING; import static org.neo4j.driver.internal.util.ValueFactory.emptyNodeValue; import static org.neo4j.driver.internal.util.ValueFactory.emptyRelationshipValue; @@ -109,7 +110,7 @@ private Session mockedSession() ConnectionProvider provider = mock( ConnectionProvider.class ); RetryLogic retryLogic = mock( RetryLogic.class ); NetworkSession session = - new NetworkSession( provider, retryLogic, defaultDatabase(), AccessMode.WRITE, new DefaultBookmarkHolder(), DEV_NULL_LOGGING ); + new NetworkSession( provider, retryLogic, defaultDatabase(), AccessMode.WRITE, new DefaultBookmarkHolder(), UNLIMITED_FETCH_SIZE, DEV_NULL_LOGGING ); return new InternalSession( session ); } } diff --git a/driver/src/test/java/org/neo4j/driver/SessionConfigTest.java b/driver/src/test/java/org/neo4j/driver/SessionConfigTest.java index ef2bf931fb..b69e319df4 100644 --- a/driver/src/test/java/org/neo4j/driver/SessionConfigTest.java +++ b/driver/src/test/java/org/neo4j/driver/SessionConfigTest.java @@ -27,6 +27,7 @@ import java.util.Arrays; import java.util.List; +import java.util.Optional; import java.util.stream.Stream; import static java.util.Collections.emptyList; @@ -53,6 +54,7 @@ void shouldReturnDefaultValues() throws Throwable assertEquals( AccessMode.WRITE, config.defaultAccessMode() ); assertFalse( config.database().isPresent() ); assertNull( config.bookmarks() ); + assertFalse( config.fetchSize().isPresent() ); } @ParameterizedTest @@ -145,4 +147,28 @@ void shouldAcceptNullInBookmarks() throws Throwable SessionConfig config2 = builder().withBookmarks( Arrays.asList( one, two, null ) ).build(); assertThat( config2.bookmarks(), equalTo( Arrays.asList( one, two, null ) ) ); } + + @ParameterizedTest + @ValueSource( longs = {100, 1, 1000, Long.MAX_VALUE, -1} ) + void shouldChangeFetchSize( long value ) throws Throwable + { + SessionConfig config = builder().withFetchSize( value ).build(); + assertThat( config.fetchSize(), equalTo( Optional.of( value ) ) ); + } + + @ParameterizedTest + @ValueSource( longs = {0, -100, -2} ) + void shouldErrorWithIllegalFetchSize( long value ) throws Throwable + { + assertThrows( IllegalArgumentException.class, () -> builder().withFetchSize( value ).build() ); + } + + @Test + void shouldTwoConfigBeEqual() throws Throwable + { + SessionConfig config1 = builder().withFetchSize( 100 ).build(); + SessionConfig config2 = builder().withFetchSize( 100 ).build(); + + assertEquals( config1, config2 ); + } } 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 c4e3e5167a..733a5a8e31 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" ).consume(); + session.run( "RETURN 1" ).summary(); assertEquals( bookmark, session.lastBookmark() ); } @@ -181,7 +181,7 @@ void bookmarkRemainsAfterFailedSessionRun() Bookmark bookmark = session.lastBookmark(); assertBookmarkContainsSingleValue( bookmark ); - assertThrows( ClientException.class, () -> session.run( "RETURN" ).consume() ); + assertThrows( ClientException.class, () -> session.run( "RETURN" ).summary() ); 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 41807328ae..448900932b 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.consume(); + result.summary(); Connection connection2 = connectionPool.lastAcquiredConnectionSpy; assertSame( connection1, connection2 ); @@ -201,7 +201,7 @@ void connectionUsedForSessionRunReturnedToThePoolWhenServerErrorDuringResultFetc Connection connection1 = connectionPool.lastAcquiredConnectionSpy; verify( connection1, never() ).release(); - assertThrows( ClientException.class, result::consume ); + assertThrows( ClientException.class, result::summary ); Connection connection2 = connectionPool.lastAcquiredConnectionSpy; assertSame( connection1, connection2 ); @@ -219,6 +219,7 @@ void connectionUsedForTransactionReturnedToThePoolWhenTransactionCommitted() verify( connection1, never() ).release(); StatementResult result = createNodes( 5, tx ); + int size = result.list().size(); tx.commit(); tx.close(); @@ -226,7 +227,7 @@ void connectionUsedForTransactionReturnedToThePoolWhenTransactionCommitted() assertSame( connection1, connection2 ); verify( connection1 ).release(); - assertEquals( 5, result.list().size() ); + assertEquals( 5, size ); } @Test @@ -240,6 +241,7 @@ void connectionUsedForTransactionReturnedToThePoolWhenTransactionRolledBack() verify( connection1, never() ).release(); StatementResult result = createNodes( 8, tx ); + int size = result.list().size(); tx.rollback(); tx.close(); @@ -247,7 +249,7 @@ void connectionUsedForTransactionReturnedToThePoolWhenTransactionRolledBack() assertSame( connection1, connection2 ); verify( connection1 ).release(); - assertEquals( 8, result.list().size() ); + assertEquals( 8, size ); } @Test 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 5bfae403b8..4a7bac1cfd 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.consume(); + result.summary(); } 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 3684d24132..8a09cddbe9 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" ).consume(); + session.run( "RETURN 1" ).summary(); } // 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" ).consume(); + session.run( "RETURN 2" ).summary(); } } 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 a47f6e9d47..af32f970a1 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.consume(); + result.summary(); } ); 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" ).consume(); } catch ( ClientException e ) {/*empty*/} + try { tx.run( "invalid" ).summary(); } 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" ).consume(); } catch ( ClientException e ) {/*empty*/} + try { session.run( "invalid" ).summary(); } 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" ).consume(); + tx.run( "invalid" ).summary(); } 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" ).consume() ); + ClientException error = assertThrows( ClientException.class, () -> session.run( "RETURN 10 / 0" ).summary() ); // 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" ).consume(); + session.run( "RETURN 1" ).summary(); 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 6c70ce0c21..d6533963bc 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::consumeAsync ) + .thenCompose( StatementResultCursor::summaryAsync ) .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 85be734b78..61357bf6bf 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 ).consume() ); + ServiceUnavailableException e = assertThrows( ServiceUnavailableException.class, () -> session.run( "RETURN {a}", value ).summary() ); 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 15e9341727..05b0832cc5 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::consume ); + assertThrows( Exception.class, res1::summary ); // When StatementResult res2 = session.run( "RETURN 1" ); @@ -144,18 +144,9 @@ void shouldBeAbleToAccessSummaryAfterFailure() ResultSummary summary; // When - try - { - res1.consume(); - } - catch ( Exception e ) - { - //ignore - } - finally - { - summary = res1.summary(); - } + assertThrows( Exception.class, res1::summary ); + summary = res1.summary(); + // Then assertThat( summary, notNullValue() ); @@ -184,7 +175,7 @@ void shouldBeAbleToAccessSummaryAfterTransactionFailure() } @Test - void shouldBufferRecordsAfterSummary() + void shouldNotBufferRecordsAfterSummary() { // Given StatementResult result = session.run("UNWIND [1,2] AS a RETURN a"); @@ -197,8 +188,7 @@ void shouldBufferRecordsAfterSummary() assertThat( summary.server().address(), equalTo( "localhost:" + session.boltPort() ) ); assertThat( summary.counters().nodesCreated(), equalTo( 0 ) ); - assertThat( result.next().get( "a" ).asInt(), equalTo( 1 ) ); - assertThat( result.next().get( "a" ).asInt(), equalTo( 2 ) ); + assertFalse( result.hasNext() ); } @Test @@ -208,7 +198,7 @@ void shouldDiscardRecordsAfterConsume() StatementResult result = session.run("UNWIND [1,2] AS a RETURN a"); // When - ResultSummary summary = result.consume(); + ResultSummary summary = result.summary(); // Then assertThat( summary, notNullValue() ); @@ -291,7 +281,7 @@ void shouldConvertEventuallyFailingStatementResultToStream() assertThat( e.getMessage(), containsString( "/ by zero" ) ); - // stream should manage to consume all elements except the last one, which produces an error + // stream should manage to summary all elements except the last one, which produces an error assertEquals( asList( 1, 1, 1, 1, 1 ), seen ); } 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 9bf1b8e941..851e4b3de9 100644 --- a/driver/src/test/java/org/neo4j/driver/integration/RoutingDriverBoltKitTest.java +++ b/driver/src/test/java/org/neo4j/driver/integration/RoutingDriverBoltKitTest.java @@ -114,8 +114,8 @@ void shouldHandleAcquireReadTransaction() throws IOException, InterruptedExcepti Session session = driver.session( builder().withDefaultAccessMode( AccessMode.READ ).build() ) ) { - List result = session.readTransaction( tx -> tx.run( "MATCH (n) RETURN n.name" ) ) - .list( record -> record.get( "n.name" ).asString() ); + List result = session.readTransaction( tx -> tx.run( "MATCH (n) RETURN n.name" ) + .list( record -> record.get( "n.name" ).asString() ) ); assertThat( result, equalTo( asList( "Bob", "Alice", "Tina" ) ) ); } @@ -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'})" ).consume() ); + assertThrows( SessionExpiredException.class, () -> session.run( "CREATE (n {name:'Bob'})" ).summary() ); } 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" ).consume() ); + assertThrows( SessionExpiredException.class, () -> tx.run( "MATCH (n) RETURN n.name" ).summary() ); } 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 ()" ).consume(); + session.run( "CREATE ()" ).summary(); } 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 ()" ).consume(); + tx.run( "CREATE ()" ).summary(); } 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'})" ).consume() ); + assertThrows( SessionExpiredException.class, () -> session.run( "CREATE (n {name:'Bob'})" ).summary() ); } finally { @@ -960,12 +960,12 @@ void shouldTreatRoutingTableWithSingleRouterAsValid() throws Exception // read multiple times without additional rediscovery StatementResult readResult1 = session.run( "MATCH (n) RETURN n.name" ); - assertEquals( "127.0.0.1:9003", readResult1.summary().server().address() ); assertEquals( 3, readResult1.list().size() ); + assertEquals( "127.0.0.1:9003", readResult1.summary().server().address() ); StatementResult readResult2 = session.run( "MATCH (n) RETURN n.name" ); - assertEquals( "127.0.0.1:9004", readResult2.summary().server().address() ); assertEquals( 3, readResult2.list().size() ); + assertEquals( "127.0.0.1:9004", readResult2.summary().server().address() ); } finally { @@ -1076,11 +1076,13 @@ void shouldUseResolverDuringRediscoveryWhenExistingRoutersFail() throws Exceptio try ( Session session = driver.session() ) { // run first query against 9001, which should return result and exit - List names1 = session.run( "MATCH (n) RETURN n.name AS name" ).list( record -> record.get( "name" ).asString() ); + List names1 = session.run( "MATCH (n) RETURN n.name AS name" ) + .list( record -> record.get( "name" ).asString() ); assertEquals( asList( "Alice", "Bob", "Eve" ), names1 ); // run second query with retries, it should rediscover using 9042 returned by the resolver and read from 9005 - List names2 = session.readTransaction( tx -> tx.run( "MATCH (n) RETURN n.name" ) ).list( record -> record.get( 0 ).asString() ); + List names2 = session.readTransaction( tx -> tx.run( "MATCH (n) RETURN n.name" ) + .list( record -> record.get( 0 ).asString() ) ); assertEquals( asList( "Bob", "Alice", "Tina" ), names2 ); } } @@ -1140,7 +1142,7 @@ void shouldRevertToInitialRouterIfKnownRouterThrowsProtocolErrors() throws Excep { try ( Session session = driver.session( builder().withDefaultAccessMode( AccessMode.READ ).build() ) ) { - List records = session.readTransaction( tx -> tx.run( "MATCH (n) RETURN n.name" ) ).list(); + List records = session.readTransaction( tx -> tx.run( "MATCH (n) RETURN n.name" ).list() ); assertEquals( 3, records.size() ); } } 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 8a3dd872d3..a1802cbbee 100644 --- a/driver/src/test/java/org/neo4j/driver/integration/SessionBoltV3IT.java +++ b/driver/src/test/java/org/neo4j/driver/integration/SessionBoltV3IT.java @@ -30,7 +30,9 @@ import java.util.Map; import java.util.concurrent.CompletionStage; +import org.neo4j.driver.Bookmark; import org.neo4j.driver.Driver; +import org.neo4j.driver.Record; import org.neo4j.driver.Session; import org.neo4j.driver.StatementResult; import org.neo4j.driver.Transaction; @@ -38,7 +40,6 @@ import org.neo4j.driver.async.AsyncSession; import org.neo4j.driver.async.StatementResultCursor; import org.neo4j.driver.exceptions.TransientException; -import org.neo4j.driver.Bookmark; import org.neo4j.driver.internal.cluster.RoutingSettings; import org.neo4j.driver.internal.messaging.Message; import org.neo4j.driver.internal.messaging.request.GoodbyeMessage; @@ -117,21 +118,21 @@ void shouldSetTransactionTimeout() { // create a dummy node Session session = driver.session(); - session.run( "CREATE (:Node)" ).consume(); + session.run( "CREATE (:Node)" ).summary(); 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" ).consume(); + otherTx.run( "MATCH (n:Node) SET n.prop = 1" ).summary(); 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 ).consume() ); + () -> session.run( "MATCH (n:Node) SET n.prop = 2", config ).summary() ); assertThat( error.getMessage(), containsString( "terminated" ) ); } ); } @@ -143,14 +144,14 @@ void shouldSetTransactionTimeoutAsync() { // create a dummy node AsyncSession asyncSession = driver.asyncSession(); - await( await( asyncSession.runAsync( "CREATE (:Node)" ) ).consumeAsync() ); + await( await( asyncSession.runAsync( "CREATE (:Node)" ) ).summaryAsync() ); 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" ).consume(); + otherTx.run( "MATCH (n:Node) SET n.prop = 1" ).summary(); assertTimeoutPreemptively( TX_TIMEOUT_TEST_TIMEOUT, () -> { TransactionConfig config = TransactionConfig.builder() @@ -159,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::consumeAsync ); + .thenCompose( StatementResultCursor::summaryAsync ); TransientException error = assertThrows( TransientException.class, () -> await( resultFuture ) ); @@ -199,18 +200,18 @@ void shouldUseBookmarksForAutoCommitTransactions() Session session = driver.session(); Bookmark initialBookmark = session.lastBookmark(); - session.run( "CREATE ()" ).consume(); + session.run( "CREATE ()" ).summary(); Bookmark bookmark1 = session.lastBookmark(); assertNotNull( bookmark1 ); assertNotEquals( initialBookmark, bookmark1 ); - session.run( "CREATE ()" ).consume(); + session.run( "CREATE ()" ).summary(); Bookmark bookmark2 = session.lastBookmark(); assertNotNull( bookmark2 ); assertNotEquals( initialBookmark, bookmark2 ); assertNotEquals( bookmark1, bookmark2 ); - session.run( "CREATE ()" ).consume(); + session.run( "CREATE ()" ).summary(); Bookmark bookmark3 = session.lastBookmark(); assertNotNull( bookmark3 ); assertNotEquals( initialBookmark, bookmark3 ); @@ -233,7 +234,7 @@ void shouldUseBookmarksForAutoCommitAndExplicitTransactions() assertNotNull( bookmark1 ); assertNotEquals( initialBookmark, bookmark1 ); - session.run( "CREATE ()" ).consume(); + session.run( "CREATE ()" ).summary(); Bookmark bookmark2 = session.lastBookmark(); assertNotNull( bookmark2 ); assertNotEquals( initialBookmark, bookmark2 ); @@ -262,7 +263,7 @@ void shouldUseBookmarksForAutoCommitTransactionsAndTransactionFunctions() assertNotNull( bookmark1 ); assertNotEquals( initialBookmark, bookmark1 ); - session.run( "CREATE ()" ).consume(); + session.run( "CREATE ()" ).summary(); Bookmark bookmark2 = session.lastBookmark(); assertNotNull( bookmark2 ); assertNotEquals( initialBookmark, bookmark2 ); @@ -329,12 +330,11 @@ private static void testTransactionMetadataWithAsyncTransactionFunctions( boolea .build(); // call listTransactions procedure that should list itself with the specified metadata - CompletionStage cursorFuture = - read ? asyncSession.readTransactionAsync( tx -> tx.runAsync( "CALL dbms.listTransactions()" ), config ) - : asyncSession.writeTransactionAsync( tx -> tx.runAsync( "CALL dbms.listTransactions()" ), config ); + CompletionStage singleFuture = + read ? asyncSession.readTransactionAsync( tx -> tx.runAsync( "CALL dbms.listTransactions()" ).thenCompose( StatementResultCursor::singleAsync ), config ) + : asyncSession.writeTransactionAsync( tx -> tx.runAsync( "CALL dbms.listTransactions()" ).thenCompose( StatementResultCursor::singleAsync ), config ); - CompletionStage> metadataFuture = cursorFuture.thenCompose( StatementResultCursor::singleAsync ) - .thenApply( record -> record.get( "metaData" ).asMap() ); + CompletionStage> metadataFuture = singleFuture.thenApply( record -> record.get( "metaData" ).asMap() ); assertEquals( metadata, await( metadataFuture ) ); } @@ -352,10 +352,10 @@ private static void testTransactionMetadataWithTransactionFunctions( boolean rea .build(); // call listTransactions procedure that should list itself with the specified metadata - StatementResult result = read ? session.readTransaction( tx -> tx.run( "CALL dbms.listTransactions()" ), config ) - : session.writeTransaction( tx -> tx.run( "CALL dbms.listTransactions()" ), config ); + Record single = read ? session.readTransaction( tx -> tx.run( "CALL dbms.listTransactions()" ).single(), config ) + : session.writeTransaction( tx -> tx.run( "CALL dbms.listTransactions()" ).single(), config ); - Map receivedMetadata = result.single().get( "metaData" ).asMap(); + Map receivedMetadata = single.get( "metaData" ).asMap(); assertEquals( metadata, receivedMetadata ); } 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 2dd7d6e1a4..c3f66019b5 100644 --- a/driver/src/test/java/org/neo4j/driver/integration/SessionIT.java +++ b/driver/src/test/java/org/neo4j/driver/integration/SessionIT.java @@ -372,8 +372,9 @@ void readTxRolledBackWithTxFailure() long answer = session.readTransaction( tx -> { StatementResult result = tx.run( "RETURN 42" ); + long single = result.single().get( 0 ).asLong(); tx.rollback(); - return result.single().get( 0 ).asLong(); + return single; } ); assertEquals( 42, answer ); @@ -599,13 +600,13 @@ void transactionRunShouldFailOnDeadlocks() throws Exception Transaction tx = session.beginTransaction() ) { // lock first node - updateNodeId( tx, nodeId1, newNodeId1 ).consume(); + updateNodeId( tx, nodeId1, newNodeId1 ).summary(); latch1.await(); latch2.countDown(); // lock second node - updateNodeId( tx, nodeId2, newNodeId1 ).consume(); + updateNodeId( tx, nodeId2, newNodeId1 ).summary(); tx.commit(); } @@ -618,13 +619,13 @@ void transactionRunShouldFailOnDeadlocks() throws Exception Transaction tx = session.beginTransaction() ) { // lock second node - updateNodeId( tx, nodeId2, newNodeId2 ).consume(); + updateNodeId( tx, nodeId2, newNodeId2 ).summary(); latch1.countDown(); latch2.await(); // lock first node - updateNodeId( tx, nodeId1, newNodeId2 ).consume(); + updateNodeId( tx, nodeId1, newNodeId2 ).summary(); tx.commit(); } @@ -665,13 +666,13 @@ void writeTransactionFunctionShouldRetryDeadlocks() throws Exception Transaction tx = session.beginTransaction() ) { // lock first node - updateNodeId( tx, nodeId1, newNodeId1 ).consume(); + updateNodeId( tx, nodeId1, newNodeId1 ).summary(); latch1.await(); latch2.countDown(); // lock second node - updateNodeId( tx, nodeId2, newNodeId1 ).consume(); + updateNodeId( tx, nodeId2, newNodeId1 ).summary(); tx.commit(); } @@ -685,13 +686,13 @@ void writeTransactionFunctionShouldRetryDeadlocks() throws Exception session.writeTransaction( tx -> { // lock second node - updateNodeId( tx, nodeId2, newNodeId2 ).consume(); + updateNodeId( tx, nodeId2, newNodeId2 ).summary(); latch1.countDown(); await( latch2 ); // lock first node - updateNodeId( tx, nodeId1, newNodeId2 ).consume(); + updateNodeId( tx, nodeId1, newNodeId2 ).summary(); createNodeWithId( nodeId3 ); @@ -786,7 +787,7 @@ void shouldPropagatePullAllFailureWhenClosed() } @Test - void shouldBePossibleToConsumeResultAfterSessionIsClosed() + void shouldNotBePossibleToConsumeResultAfterSessionIsClosed() { StatementResult result; try ( Session session = neo4j.driver().session() ) @@ -795,7 +796,7 @@ void shouldBePossibleToConsumeResultAfterSessionIsClosed() } List ints = result.list( record -> record.get( 0 ).asInt() ); - assertEquals( 20000, ints.size() ); + assertEquals( 0, ints.size() ); } @Test @@ -851,7 +852,7 @@ void shouldCloseCleanlyWhenRunErrorConsumed() session.run( "CREATE ()" ); - ClientException e = assertThrows( ClientException.class, () -> session.run( "RETURN 10 / 0" ).consume() ); + ClientException e = assertThrows( ClientException.class, () -> session.run( "RETURN 10 / 0" ).summary() ); assertThat( e.getMessage(), containsString( "/ by zero" ) ); session.run( "CREATE ()" ); @@ -899,7 +900,7 @@ void shouldNotRetryOnConnectionAcquisitionTimeout() } @Test - void shouldAllowConsumingRecordsAfterFailureInSessionClose() + void shouldNotAllowConsumingRecordsAfterFailureInSessionClose() { Session session = neo4j.driver().session(); @@ -908,17 +909,11 @@ void shouldAllowConsumingRecordsAfterFailureInSessionClose() ClientException e = assertThrows( ClientException.class, session::close ); assertThat( e, is( arithmeticError() ) ); - assertTrue( result.hasNext() ); - assertEquals( 16, result.next().get( 0 ).asInt() ); - assertTrue( result.hasNext() ); - assertEquals( 8, result.next().get( 0 ).asInt() ); - assertTrue( result.hasNext() ); - assertEquals( 4, result.next().get( 0 ).asInt() ); assertFalse( result.hasNext() ); } @Test - void shouldAllowAccessingRecordsAfterSummary() + void shouldNotAllowAccessingRecordsAfterSummary() { int recordCount = 10_000; String query = "UNWIND range(1, " + recordCount + ") AS x RETURN x"; @@ -932,17 +927,12 @@ void shouldAllowAccessingRecordsAfterSummary() assertEquals( StatementType.READ_ONLY, summary.statementType() ); List records = result.list(); - assertEquals( recordCount, records.size() ); - for ( int i = 1; i <= recordCount; i++ ) - { - Record record = records.get( i - 1 ); - assertEquals( i, record.get( 0 ).asInt() ); - } + assertEquals( 0, records.size() ); } } @Test - void shouldAllowAccessingRecordsAfterSessionClosed() + void shouldNotAllowAccessingRecordsAfterSessionClosed() { int recordCount = 11_333; String query = "UNWIND range(1, " + recordCount + ") AS x RETURN 'Result-' + x"; @@ -954,12 +944,7 @@ void shouldAllowAccessingRecordsAfterSessionClosed() } List records = result.list(); - assertEquals( recordCount, records.size() ); - for ( int i = 1; i <= recordCount; i++ ) - { - Record record = records.get( i - 1 ); - assertEquals( "Result-" + i, record.get( 0 ).asString() ); - } + assertEquals( 0, records.size() ); } @Test @@ -969,7 +954,7 @@ void shouldAllowToConsumeRecordsSlowlyAndCloseSession() throws InterruptedExcept StatementResult result = session.run( "UNWIND range(10000, 0, -1) AS x RETURN 10 / x" ); - // consume couple records slowly with a sleep in-between + // summary couple records slowly with a sleep in-between for ( int i = 0; i < 10; i++ ) { assertTrue( result.hasNext() ); @@ -988,7 +973,7 @@ void shouldAllowToConsumeRecordsSlowlyAndRetrieveSummary() throws InterruptedExc { StatementResult result = session.run( "UNWIND range(8000, 1, -1) AS x RETURN 42 / x" ); - // consume couple records slowly with a sleep in-between + // summary couple records slowly with a sleep in-between for ( int i = 0; i < 12; i++ ) { assertTrue( result.hasNext() ); @@ -1007,10 +992,10 @@ void shouldBeResponsiveToThreadInterruptWhenWaitingForResult() try ( Session session1 = neo4j.driver().session(); Session session2 = neo4j.driver().session() ) { - session1.run( "CREATE (:Person {name: 'Beta Ray Bill'})" ).consume(); + session1.run( "CREATE (:Person {name: 'Beta Ray Bill'})" ).summary(); Transaction tx = session1.beginTransaction(); - tx.run( "MATCH (n:Person {name: 'Beta Ray Bill'}) SET n.hammer = 'Mjolnir'" ).consume(); + tx.run( "MATCH (n:Person {name: 'Beta Ray Bill'}) SET n.hammer = 'Mjolnir'" ).summary(); // now 'Beta Ray Bill' node is locked @@ -1020,7 +1005,7 @@ void shouldBeResponsiveToThreadInterruptWhenWaitingForResult() try { ServiceUnavailableException e = assertThrows( ServiceUnavailableException.class, - () -> session2.run( "MATCH (n:Person {name: 'Beta Ray Bill'}) SET n.hammer = 'Stormbreaker'" ).consume() ); + () -> session2.run( "MATCH (n:Person {name: 'Beta Ray Bill'}) SET n.hammer = 'Stormbreaker'" ).summary() ); assertThat( e.getMessage(), containsString( "Connection to the database terminated" ) ); assertThat( e.getMessage(), containsString( "Thread interrupted" ) ); } @@ -1046,10 +1031,10 @@ void shouldAllowLongRunningQueryWithConnectTimeout() throws Exception Session session1 = driver.session(); Session session2 = driver.session(); - session1.run( "CREATE (:Avenger {name: 'Hulk'})" ).consume(); + session1.run( "CREATE (:Avenger {name: 'Hulk'})" ).summary(); Transaction tx = session1.beginTransaction(); - tx.run( "MATCH (a:Avenger {name: 'Hulk'}) SET a.power = 100 RETURN a" ).consume(); + tx.run( "MATCH (a:Avenger {name: 'Hulk'}) SET a.power = 100 RETURN a" ).summary(); // Hulk node is now locked @@ -1102,7 +1087,7 @@ void shouldAllowConsumingEmptyResult() try ( Session session = neo4j.driver().session() ) { StatementResult result = session.run( "UNWIND [] AS x RETURN x" ); - ResultSummary summary = result.consume(); + ResultSummary summary = result.summary(); assertNotNull( summary ); assertEquals( StatementType.READ_ONLY, summary.statementType() ); } @@ -1126,7 +1111,7 @@ void shouldConsume() String query = "UNWIND [1, 2, 3, 4, 5] AS x RETURN x"; StatementResult result = session.run( query ); - ResultSummary summary = result.consume(); + ResultSummary summary = result.summary(); assertEquals( query, summary.statement().text() ); assertEquals( StatementType.READ_ONLY, summary.statementType() ); @@ -1143,7 +1128,7 @@ void shouldConsumeWithFailure() String query = "UNWIND [1, 2, 3, 4, 0] AS x RETURN 10 / x"; StatementResult result = session.run( query ); - ClientException e = assertThrows( ClientException.class, result::consume ); + ClientException e = assertThrows( ClientException.class, result::summary ); assertThat( e, is( arithmeticError() ) ); assertFalse( result.hasNext() ); @@ -1213,8 +1198,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})" ).consume(); - session.run( "UNWIND range(1, 10) AS x CREATE (:Resource {id: x})" ).consume(); + 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(); int seenProperties = 0; int seenResources = 0; @@ -1324,7 +1309,7 @@ void shouldErrorDatabaseWhenDatabaseIsAbsent() throws Throwable ClientException error = assertThrows( ClientException.class, () -> { StatementResult result = session.run( "RETURN 1" ); - result.consume(); + result.summary(); } ); assertThat( error.getMessage(), containsString( "Database does not exist. Database name: 'foo'" ) ); @@ -1342,7 +1327,7 @@ void shouldErrorDatabaseNameUsingTxWhenDatabaseIsAbsent() throws Throwable ClientException error = assertThrows( ClientException.class, () -> { Transaction transaction = session.beginTransaction(); StatementResult result = transaction.run( "RETURN 1" ); - result.consume(); + result.summary(); }); assertThat( error.getMessage(), containsString( "Database does not exist. Database name: 'foo'" ) ); session.close(); @@ -1357,7 +1342,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" ).consume() ); + session.readTransaction( tx -> tx.run( "RETURN 1" ).summary() ); }); assertThat( error.getMessage(), containsString( "Database does not exist. Database name: 'foo'" ) ); session.close(); @@ -1402,8 +1387,8 @@ private void testExecuteWriteTx( AccessMode sessionMode ) String material = session.writeTransaction( tx -> { StatementResult result = tx.run( "CREATE (s:Shield {material: 'Vibranium'}) RETURN s" ); - tx.commit(); Record record = result.single(); + tx.commit(); return record.get( 0 ).asNode().get( "material" ).asString(); } ); @@ -1574,8 +1559,9 @@ public Record execute( Transaction tx ) { throw new ServiceUnavailableException( "" ); } + Record single = result.single(); tx.commit(); - return result.single(); + return single; } } } 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 70274c759f..cb6dbae22b 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})" ).consume(); - session.run( "CREATE (:Node {id: 42})" ).consume(); + session.run( "UNWIND [1,1,2] AS x CREATE (:Node {id: x})" ).summary(); + session.run( "CREATE (:Node {id: 42})" ).summary(); 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 0cc5eafe47..2899c1913c 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::consume ); + Neo4jException e3 = assertThrows( Neo4jException.class, result::summary ); 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::consume ); + Neo4jException e = assertThrows( Neo4jException.class, procedureResult::summary ); 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.consume(); // blocking to run the statement + result.summary(); // blocking to run the statement } } ); @@ -338,13 +338,13 @@ void shouldAllowMoreStatementAfterSessionReset() try ( Session session = neo4j.driver().session() ) { - session.run( "RETURN 1" ).consume(); + session.run( "RETURN 1" ).summary(); // When reset the state of this session session.reset(); // Then can run successfully more statements without any error - session.run( "RETURN 2" ).consume(); + session.run( "RETURN 2" ).summary(); } } @@ -427,7 +427,7 @@ void performUpdate( Driver driver, int nodeId, int newNodeId, usedSessionRef.set( session ); latchToWait.await(); StatementResult result = updateNodeId( session, nodeId, newNodeId ); - result.consume(); + result.summary(); } } } ); @@ -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.consume(); + result.summary(); } } } ); @@ -474,7 +474,7 @@ public void performUpdate( Driver driver, int nodeId, int newNodeId, { invocationsOfWork.incrementAndGet(); StatementResult result = updateNodeId( tx, nodeId, newNodeId ); - result.consume(); + result.summary(); 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.consume(); + result.summary(); 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 ).consume(); + session.run( query ).summary(); } else { 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 b9a7b16570..75733d3a6e 100644 --- a/driver/src/test/java/org/neo4j/driver/integration/SummaryIT.java +++ b/driver/src/test/java/org/neo4j/driver/integration/SummaryIT.java @@ -65,7 +65,7 @@ void shouldContainBasicMetadata() assertTrue( result.hasNext() ); // When - ResultSummary summary = result.consume(); + ResultSummary summary = result.summary(); // Then assertFalse( result.hasNext() ); @@ -74,7 +74,7 @@ void shouldContainBasicMetadata() assertThat( summary.statement().parameters(), equalTo( statementParameters ) ); assertFalse( summary.hasPlan() ); assertFalse( summary.hasProfile() ); - assertThat( summary, equalTo( result.consume() ) ); + assertThat( summary, equalTo( result.summary() ) ); } @@ -82,7 +82,7 @@ void shouldContainBasicMetadata() void shouldContainTimeInformation() { // Given - ResultSummary summary = session.run( "UNWIND range(1,1000) AS n RETURN n AS number" ).consume(); + ResultSummary summary = session.run( "UNWIND range(1,1000) AS n RETURN n AS number" ).summary(); // Then assertThat( summary.resultAvailableAfter( TimeUnit.MILLISECONDS ), greaterThanOrEqualTo( 0L ) ); @@ -92,40 +92,40 @@ void shouldContainTimeInformation() @Test void shouldContainCorrectStatistics() { - 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 (n)" ).summary().counters().nodesCreated(), equalTo( 1 ) ); + assertThat( session.run( "MATCH (n) DELETE (n)" ).summary().counters().nodesDeleted(), 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 ()-[:KNOWS]->()" ).summary().counters().relationshipsCreated(), equalTo( 1 ) ); + assertThat( session.run( "MATCH ()-[r:KNOWS]->() DELETE r" ).summary().counters().relationshipsDeleted(), 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 (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 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 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 CONSTRAINT ON (book:Book) ASSERT book.isbn IS UNIQUE" ) - .consume().counters().constraintsAdded(), equalTo( 1 ) ); + .summary().counters().constraintsAdded(), equalTo( 1 ) ); assertThat( session.run( "DROP CONSTRAINT ON (book:Book) ASSERT book.isbn IS UNIQUE" ) - .consume().counters().constraintsRemoved(), equalTo( 1 ) ); + .summary().counters().constraintsRemoved(), equalTo( 1 ) ); } @Test void shouldContainCorrectStatementType() { - 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 )); + 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 )); } @Test void shouldContainCorrectPlan() { // When - ResultSummary summary = session.run( "EXPLAIN MATCH (n) RETURN 1" ).consume(); + ResultSummary summary = session.run( "EXPLAIN MATCH (n) RETURN 1" ).summary(); // Then assertTrue( summary.hasPlan() ); @@ -141,7 +141,7 @@ void shouldContainCorrectPlan() void shouldContainProfile() { // When - ResultSummary summary = session.run( "PROFILE RETURN 1" ).consume(); + ResultSummary summary = session.run( "PROFILE RETURN 1" ).summary(); // Then assertTrue( summary.hasProfile() ); @@ -158,7 +158,7 @@ void shouldContainProfile() void shouldContainNotifications() { // When - ResultSummary summary = session.run( "EXPLAIN MATCH (n:ThisLabelDoesNotExist) RETURN n" ).consume(); + ResultSummary summary = session.run( "EXPLAIN MATCH (n:ThisLabelDoesNotExist) RETURN n" ).summary(); // Then List notifications = summary.notifications(); @@ -176,7 +176,7 @@ void shouldContainNotifications() void shouldContainNoNotifications() throws Throwable { // When - ResultSummary summary = session.run( "RETURN 1" ).consume(); + ResultSummary summary = session.run( "RETURN 1" ).summary(); // 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 cf95b19885..e0136c8c45 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" ).consume(); + tx.run( "RETURN 1" ).summary(); verifyTransactionMetadata( metadata ); } @@ -89,7 +89,7 @@ void shouldSetTransactionMetadataAsync() CompletionStage txFuture = driver.asyncSession().beginTransactionAsync( config ) .thenCompose( tx -> tx.runAsync( "RETURN 1" ) - .thenCompose( StatementResultCursor::consumeAsync ) + .thenCompose( StatementResultCursor::summaryAsync ) .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)" ).consume(); + session.run( "CREATE (:Node)" ).summary(); 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" ).consume(); + otherTx.run( "MATCH (n:Node) SET n.prop = 1" ).summary(); 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)" ).consume(); + session.run( "CREATE (:Node)" ).summary(); 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" ).consume(); + otherTx.run( "MATCH (n:Node) SET n.prop = 1" ).summary(); 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 b94c427ee0..5c219d93fe 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.consume(); + result.summary(); tx.commit(); tx.close(); // WHEN when running a malformed query in the original session - assertThrows( ClientException.class, () -> session.run( "CREAT (n) RETURN n" ).consume() ); + assertThrows( ClientException.class, () -> session.run( "CREAT (n) RETURN n" ).summary() ); } @SuppressWarnings( "ConstantConditions" ) @@ -308,7 +308,7 @@ void shouldRollBackTxIfErrorWithConsume() try ( Transaction tx = session.beginTransaction() ) { StatementResult result = tx.run( "invalid" ); - result.consume(); + result.summary(); } } ); @@ -338,11 +338,11 @@ void shouldBeResponsiveToThreadInterruptWhenWaitingForResult() { try ( Session otherSession = session.driver().session() ) { - session.run( "CREATE (:Person {name: 'Beta Ray Bill'})" ).consume(); + session.run( "CREATE (:Person {name: 'Beta Ray Bill'})" ).summary(); Transaction tx1 = session.beginTransaction(); Transaction tx2 = otherSession.beginTransaction(); - tx1.run( "MATCH (n:Person {name: 'Beta Ray Bill'}) SET n.hammer = 'Mjolnir'" ).consume(); + tx1.run( "MATCH (n:Person {name: 'Beta Ray Bill'}) SET n.hammer = 'Mjolnir'" ).summary(); // 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'" ).consume() ); + () -> tx2.run( "MATCH (n:Person {name: 'Beta Ray Bill'}) SET n.hammer = 'Stormbreaker'" ).summary() ); 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'})" ).consume(); + session.run( "CREATE (:Person {name: 'Beta Ray Bill'})" ).summary(); Transaction tx1 = session.beginTransaction(); Transaction tx2 = otherSession.beginTransaction(); - tx1.run( "MATCH (n:Person {name: 'Beta Ray Bill'}) SET n.hammer = 'Mjolnir'" ).consume(); + tx1.run( "MATCH (n:Person {name: 'Beta Ray Bill'}) SET n.hammer = 'Mjolnir'" ).summary(); // 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})" ).consume(); + tx.run( "CREATE (:MyNode {id: 1})" ).summary(); // kill all network channels for ( Channel channel: factory.channels() ) @@ -415,7 +415,7 @@ void shouldThrowWhenConnectionKilledDuringTransaction() channel.close().syncUninterruptibly(); } - tx.run( "CREATE (:MyNode {id: 1})" ).consume(); + tx.run( "CREATE (:MyNode {id: 1})" ).summary(); } } ); @@ -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" ).consume() ); + ClientException error1 = assertThrows( ClientException.class, () -> tx.run( "RETURN unknown" ).summary() ); 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" ).consume() ); + ClientException error1 = assertThrows( ClientException.class, () -> tx.run( "RETURN unknown" ).summary() ); assertThat( error1.code(), containsString( "SyntaxError" ) ); - ClientException error2 = assertThrows( ClientException.class, () -> tx.run( "CREATE (:OtherNode)" ).consume() ); + ClientException error2 = assertThrows( ClientException.class, () -> tx.run( "CREATE (:OtherNode)" ).summary() ); assertThat( error2.getMessage(), startsWith( "Cannot run more statements in this transaction" ) ); - ClientException error3 = assertThrows( ClientException.class, () -> tx.run( "RETURN 42" ).consume() ); + ClientException error3 = assertThrows( ClientException.class, () -> tx.run( "RETURN 42" ).summary() ); 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 a31df4de5a..416791ad2c 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 @@ -73,8 +73,8 @@ import static org.junit.jupiter.api.Assertions.assertNull; import static org.junit.jupiter.api.Assertions.assertThrows; import static org.junit.jupiter.api.Assertions.assertTrue; -import static org.neo4j.driver.Values.parameters; import static org.neo4j.driver.SessionConfig.builder; +import static org.neo4j.driver.Values.parameters; import static org.neo4j.driver.internal.util.Futures.failedFuture; import static org.neo4j.driver.internal.util.Iterables.single; import static org.neo4j.driver.internal.util.Matchers.arithmeticError; @@ -638,7 +638,7 @@ void shouldBeginTxAfterRunFailureToAcquireConnection() assertThrows( ServiceUnavailableException.class, () -> { StatementResultCursor cursor = await( session.runAsync( "RETURN 42" ) ); - await( cursor.consumeAsync() ); + await( cursor.summaryAsync() ); } ); neo4j.startDb(); @@ -801,7 +801,7 @@ void shouldCloseCleanlyWhenRunErrorConsumed() { StatementResultCursor cursor = await( session.runAsync( "SomeWrongQuery" ) ); - ClientException e = assertThrows( ClientException.class, () -> await( cursor.consumeAsync() ) ); + ClientException e = assertThrows( ClientException.class, () -> await( cursor.summaryAsync() ) ); assertThat( e.getMessage(), startsWith( "Invalid input" ) ); assertNull( await( session.closeAsync() ) ); } @@ -811,13 +811,13 @@ void shouldCloseCleanlyWhenPullAllErrorConsumed() { StatementResultCursor cursor = await( session.runAsync( "UNWIND range(10, 0, -1) AS x RETURN 1 / x" ) ); - ClientException e = assertThrows( ClientException.class, () -> await( cursor.consumeAsync() ) ); + ClientException e = assertThrows( ClientException.class, () -> await( cursor.summaryAsync() ) ); assertThat( e.getMessage(), containsString( "/ by zero" ) ); assertNull( await( session.closeAsync() ) ); } @Test - void shouldBePossibleToConsumeResultAfterSessionIsClosed() + void shouldNotBePossibleToConsumeResultAfterSessionIsClosed() { CompletionStage cursorStage = session.runAsync( "UNWIND range(1, 20000) AS x RETURN x" ); @@ -825,7 +825,7 @@ void shouldBePossibleToConsumeResultAfterSessionIsClosed() StatementResultCursor cursor = await( cursorStage ); List ints = await( cursor.listAsync( record -> record.get( 0 ).asInt() ) ); - assertEquals( 20000, ints.size() ); + assertEquals( 0, ints.size() ); } @Test @@ -879,7 +879,7 @@ void shouldPropagateFailureFromFirstIllegalQuery() } @Test - void shouldAllowAccessingRecordsAfterSummary() + void shouldNotAllowAccessingRecordsAfterSummary() { int recordCount = 10_000; String query = "UNWIND range(1, " + recordCount + ") AS x RETURN 'Hello-' + x"; @@ -899,16 +899,11 @@ void shouldAllowAccessingRecordsAfterSummary() assertEquals( query, summary.statement().text() ); assertEquals( StatementType.READ_ONLY, summary.statementType() ); - assertEquals( recordCount, records.size() ); - for ( int i = 1; i <= recordCount; i++ ) - { - Record record = records.get( i - 1 ); - assertEquals( "Hello-" + i, record.get( 0 ).asString() ); - } + assertEquals( 0, records.size() ); } @Test - void shouldAllowAccessingRecordsAfterSessionClosed() + void shouldNotAllowAccessingRecordsAfterSessionClosed() { int recordCount = 7_500; String query = "UNWIND range(1, " + recordCount + ") AS x RETURN x"; @@ -919,12 +914,7 @@ void shouldAllowAccessingRecordsAfterSessionClosed() List records = await( recordsStage ); - assertEquals( recordCount, records.size() ); - for ( int i = 1; i <= recordCount; i++ ) - { - Record record = records.get( i - 1 ); - assertEquals( i, record.get( 0 ).asInt() ); - } + assertEquals( 0, records.size() ); } @Test @@ -944,9 +934,9 @@ void shouldReturnNoRecordsWhenConsumed() CompletionStage summaryAndRecordStage = session.runAsync( query ) .thenCompose( cursor -> { - CompletionStage consumeStage = cursor.consumeAsync(); + CompletionStage summaryStage = cursor.summaryAsync(); CompletionStage recordStage = cursor.nextAsync(); - return consumeStage.thenCombine( recordStage, SummaryAndRecords::new ); + return summaryStage.thenCombine( recordStage, SummaryAndRecords::new ); } ); SummaryAndRecords result = await( summaryAndRecordStage ); @@ -966,11 +956,11 @@ void shouldStopReturningRecordsAfterConsumed() .thenCompose( cursor -> cursor.nextAsync() // fetch just a single record .thenCompose( record1 -> { - // then consume rest - CompletionStage consumeStage = cursor.consumeAsync(); + // then summary rest + CompletionStage summaryStage = cursor.summaryAsync(); // and try to fetch another record CompletionStage record2Stage = cursor.nextAsync(); - return consumeStage.thenCombine( record2Stage, + return summaryStage.thenCombine( record2Stage, ( summary, record2 ) -> new SummaryAndRecords( summary, record1, record2 ) ); } ) ); @@ -994,9 +984,9 @@ void shouldReturnEmptyListOfRecordsWhenConsumed() CompletionStage summaryAndRecordsStage = session.runAsync( query ) .thenCompose( cursor -> { - CompletionStage consumeStage = cursor.consumeAsync(); + CompletionStage summaryStage = cursor.summaryAsync(); CompletionStage> recordsStage = cursor.listAsync(); - return consumeStage.thenCombine( recordsStage, SummaryAndRecords::new ); + return summaryStage.thenCombine( recordsStage, SummaryAndRecords::new ); } ); SummaryAndRecords result = await( summaryAndRecordsStage ); @@ -1099,13 +1089,13 @@ private void testList( String query, List expectedList ) private void testConsume( String query ) { StatementResultCursor cursor = await( session.runAsync( query ) ); - ResultSummary summary = await( cursor.consumeAsync() ); + ResultSummary summary = await( cursor.summaryAsync() ); assertNotNull( summary ); assertEquals( query, summary.statement().text() ); assertEquals( emptyMap(), summary.statement().parameters().asMap() ); - // no records should be available, they should all be consumed + // no records should be available, they should all be summaryd assertNull( await( cursor.nextAsync() ) ); } 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 5458ea8808..2881820534 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 @@ -220,7 +220,7 @@ void shouldFailToCommitAfterSingleWrongStatement() StatementResultCursor cursor = await( tx.runAsync( "RETURN" ) ); - Exception e = assertThrows( Exception.class, () -> await( cursor.consumeAsync() ) ); + Exception e = assertThrows( Exception.class, () -> await( cursor.summaryAsync() ) ); assertThat( e, is( syntaxError( "Unexpected end of input" ) ) ); assertThrows( ClientException.class, () -> await( tx.commitAsync() ) ); @@ -255,7 +255,7 @@ void shouldFailToCommitAfterCoupleCorrectAndSingleWrongStatement() StatementResultCursor cursor3 = await( tx.runAsync( "RETURN" ) ); - Exception e = assertThrows( Exception.class, () -> await( cursor3.consumeAsync() ) ); + Exception e = assertThrows( Exception.class, () -> await( cursor3.summaryAsync() ) ); assertThat( e, is( syntaxError( "Unexpected end of input" ) ) ); assertThrows( ClientException.class, () -> await( tx.commitAsync() ) ); @@ -773,7 +773,7 @@ void shouldFailToCommitWhenRunFailureIsConsumed() AsyncTransaction tx = await( session.beginTransactionAsync() ); StatementResultCursor cursor = await( tx.runAsync( "RETURN Wrong" ) ); - ClientException e1 = assertThrows( ClientException.class, () -> await( cursor.consumeAsync() ) ); + ClientException e1 = assertThrows( ClientException.class, () -> await( cursor.summaryAsync() ) ); assertThat( e1.code(), containsString( "SyntaxError" ) ); ClientException e2 = assertThrows( ClientException.class, () -> await( tx.commitAsync() ) ); @@ -787,7 +787,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.consumeAsync() ) ); + ClientException e1 = assertThrows( ClientException.class, () -> await( cursor.summaryAsync() ) ); assertThat( e1.code(), containsString( "TypeError" ) ); ClientException e2 = assertThrows( ClientException.class, () -> await( tx.commitAsync() ) ); @@ -800,7 +800,7 @@ void shouldRollbackWhenRunFailureIsConsumed() AsyncTransaction tx = await( session.beginTransactionAsync() ); StatementResultCursor cursor = await( tx.runAsync( "RETURN Wrong" ) ); - ClientException e = assertThrows( ClientException.class, () -> await( cursor.consumeAsync() ) ); + ClientException e = assertThrows( ClientException.class, () -> await( cursor.summaryAsync() ) ); assertThat( e.code(), containsString( "SyntaxError" ) ); assertNull( await( tx.rollbackAsync() ) ); } @@ -811,7 +811,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.consumeAsync() ) ); + ClientException e = assertThrows( ClientException.class, () -> await( cursor.summaryAsync() ) ); assertThat( e.getMessage(), containsString( "/ by zero" ) ); assertNull( await( tx.rollbackAsync() ) ); } @@ -866,7 +866,7 @@ private void testConsume( String query ) { AsyncTransaction tx = await( session.beginTransactionAsync() ); StatementResultCursor cursor = await( tx.runAsync( query ) ); - ResultSummary summary = await( cursor.consumeAsync() ); + ResultSummary summary = await( cursor.summaryAsync() ); assertNotNull( summary ); assertEquals( query, summary.statement().text() ); 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 b5b4b47241..9978203924 100644 --- a/driver/src/test/java/org/neo4j/driver/internal/DirectDriverBoltKitTest.java +++ b/driver/src/test/java/org/neo4j/driver/internal/DirectDriverBoltKitTest.java @@ -46,6 +46,7 @@ import org.neo4j.driver.internal.util.Clock; import org.neo4j.driver.internal.util.io.ChannelTrackingDriverFactory; import org.neo4j.driver.reactive.RxSession; +import org.neo4j.driver.reactive.RxStatementResult; import org.neo4j.driver.util.StubServer; import static java.util.Arrays.asList; @@ -66,6 +67,7 @@ import static org.neo4j.driver.Values.parameters; import static org.neo4j.driver.internal.logging.DevNullLogging.DEV_NULL_LOGGING; import static org.neo4j.driver.util.StubServer.INSECURE_CONFIG; +import static org.neo4j.driver.util.StubServer.insecureBuilder; class DirectDriverBoltKitTest { @@ -241,6 +243,86 @@ void shouldPropagateTransactionRollbackErrorWhenSessionClosed() throws Exception } } + @Test + void shouldStreamingRecordsInBatchesRx() throws Exception + { + StubServer server = StubServer.start( "streaming_records_v4_rx.script", 9001 ); + try + { + try ( Driver driver = GraphDatabase.driver( "bolt://localhost:9001", INSECURE_CONFIG ) ) + { + RxSession session = driver.rxSession(); + RxStatementResult result = session.run( "MATCH (n) RETURN n.name" ); + Flux records = Flux.from( result.records() ).limitRate( 2 ).map( record -> record.get( "n.name" ).asString() ); + StepVerifier.create( records ).expectNext( "Bob", "Alice", "Tina" ).verifyComplete(); + } + } + finally + { + assertEquals( 0, server.exitStatus() ); + } + } + + @Test + void shouldStreamingRecordsInBatches() throws Exception + { + StubServer server = StubServer.start( "streaming_records_v4.script", 9001 ); + try + { + try ( Driver driver = GraphDatabase.driver( "bolt://localhost:9001", insecureBuilder().withFetchSize( 2 ).build() ) ) + { + Session session = driver.session(); + StatementResult result = session.run( "MATCH (n) RETURN n.name" ); + List list = result.list( record -> record.get( "n.name" ).asString() ); + assertEquals( list, asList( "Bob", "Alice", "Tina" ) ); + } + } + finally + { + assertEquals( 0, server.exitStatus() ); + } + } + + @Test + void shouldChangeFetchSize() throws Exception + { + StubServer server = StubServer.start( "streaming_records_v4.script", 9001 ); + try + { + try ( Driver driver = GraphDatabase.driver( "bolt://localhost:9001", INSECURE_CONFIG ) ) + { + Session session = driver.session( builder().withFetchSize( 2 ).build() ); + StatementResult result = session.run( "MATCH (n) RETURN n.name" ); + List list = result.list( record -> record.get( "n.name" ).asString() ); + assertEquals( list, asList( "Bob", "Alice", "Tina" ) ); + } + } + finally + { + assertEquals( 0, server.exitStatus() ); + } + } + + @Test + void shouldAllowPullAll() throws Exception + { + StubServer server = StubServer.start( "streaming_records_v4_all.script", 9001 ); + try + { + try ( Driver driver = GraphDatabase.driver( "bolt://localhost:9001", insecureBuilder().withFetchSize( -1 ).build() ) ) + { + Session session = driver.session(); + StatementResult result = session.run( "MATCH (n) RETURN n.name" ); + List list = result.list( record -> record.get( "n.name" ).asString() ); + assertEquals( list, asList( "Bob", "Alice", "Tina" ) ); + } + } + finally + { + assertEquals( 0, server.exitStatus() ); + } + } + @Test void shouldThrowCommitErrorWhenTransactionCommit() throws Exception { @@ -273,7 +355,7 @@ void shouldThrowCorrectErrorOnRunFailure() throws Throwable { TransientException error = assertThrows( TransientException.class, () -> { StatementResult result = transaction.run( "RETURN 1" ); - result.consume(); + result.summary(); } ); assertThat( error.code(), equalTo( "Neo.TransientError.General.DatabaseUnavailable" ) ); } @@ -293,7 +375,7 @@ void shouldThrowCorrectErrorOnCommitFailure() throws Throwable { Transaction transaction = session.beginTransaction(); StatementResult result = transaction.run( "CREATE (n {name:'Bob'})" ); - result.consume(); + result.summary(); TransientException error = assertThrows( TransientException.class, transaction::commit ); assertThat( error.code(), equalTo( "Neo.TransientError.General.DatabaseUnavailable" ) ); @@ -313,7 +395,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.consume(); + result.summary(); } finally { 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 69655f296a..cd16dd0b30 100644 --- a/driver/src/test/java/org/neo4j/driver/internal/InternalStatementResultTest.java +++ b/driver/src/test/java/org/neo4j/driver/internal/InternalStatementResultTest.java @@ -31,10 +31,11 @@ import org.neo4j.driver.Value; import org.neo4j.driver.async.StatementResultCursor; import org.neo4j.driver.exceptions.NoSuchRecordException; -import org.neo4j.driver.internal.async.AsyncStatementResultCursor; +import org.neo4j.driver.internal.cursor.AsyncStatementResultCursorImpl; +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.internal.handlers.RunResponseHandler; -import org.neo4j.driver.internal.handlers.SessionPullAllResponseHandler; import org.neo4j.driver.internal.spi.Connection; import org.neo4j.driver.internal.value.NullValue; import org.neo4j.driver.util.Pair; @@ -182,7 +183,7 @@ void singleShouldThrowOnConsumedResult() assertThrows( NoSuchRecordException.class, () -> { StatementResult result = createResult( 2 ); - result.consume(); + result.summary(); result.single(); } ); } @@ -192,10 +193,10 @@ void shouldConsumeTwice() { // GIVEN StatementResult result = createResult( 2 ); - result.consume(); + result.summary(); // WHEN - result.consume(); + result.summary(); // THEN assertFalse( result.hasNext() ); @@ -358,7 +359,7 @@ private StatementResult createResult( int numberOfRecords ) when( connection.serverAddress() ).thenReturn( LOCAL_DEFAULT ); when( connection.serverVersion() ).thenReturn( anyServerVersion() ); PullAllResponseHandler pullAllHandler = - new SessionPullAllResponseHandler( statement, runHandler, connection, BookmarkHolder.NO_OP, METADATA_EXTRACTOR ); + new LegacyPullAllResponseHandler( statement, runHandler, connection, METADATA_EXTRACTOR, mock( PullResponseCompletionListener.class ) ); for ( int i = 1; i <= numberOfRecords; i++ ) { @@ -366,7 +367,7 @@ private StatementResult createResult( int numberOfRecords ) } pullAllHandler.onSuccess( emptyMap() ); - StatementResultCursor cursor = new AsyncStatementResultCursor( runHandler, pullAllHandler ); + StatementResultCursor cursor = new AsyncStatementResultCursorImpl( runHandler, pullAllHandler ); return new InternalStatementResult( connection, cursor ); } 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 8b9e9093ee..f75f7bf130 100644 --- a/driver/src/test/java/org/neo4j/driver/internal/InternalTransactionTest.java +++ b/driver/src/test/java/org/neo4j/driver/internal/InternalTransactionTest.java @@ -131,7 +131,7 @@ void shouldRollback() throws Throwable void shouldRollbackWhenFailedRun() throws Throwable { setupFailingRun( connection, new RuntimeException( "Bang!" ) ); - assertThrows( RuntimeException.class, () -> tx.run( "RETURN 1" ).consume() ); + assertThrows( RuntimeException.class, () -> tx.run( "RETURN 1" ).summary() ); tx.close(); diff --git a/driver/src/test/java/org/neo4j/driver/internal/SessionFactoryImplTest.java b/driver/src/test/java/org/neo4j/driver/internal/SessionFactoryImplTest.java index 0305b9d8f4..64d104e821 100644 --- a/driver/src/test/java/org/neo4j/driver/internal/SessionFactoryImplTest.java +++ b/driver/src/test/java/org/neo4j/driver/internal/SessionFactoryImplTest.java @@ -22,8 +22,8 @@ import org.neo4j.driver.AccessMode; import org.neo4j.driver.Config; -import org.neo4j.driver.internal.async.NetworkSession; import org.neo4j.driver.internal.async.LeakLoggingNetworkSession; +import org.neo4j.driver.internal.async.NetworkSession; import org.neo4j.driver.internal.spi.ConnectionProvider; import org.neo4j.driver.internal.util.FixedRetryLogic; diff --git a/driver/src/test/java/org/neo4j/driver/internal/async/AsyncStatementResultCursorTest.java b/driver/src/test/java/org/neo4j/driver/internal/async/AsyncStatementResultCursorImplTest.java similarity index 86% rename from driver/src/test/java/org/neo4j/driver/internal/async/AsyncStatementResultCursorTest.java rename to driver/src/test/java/org/neo4j/driver/internal/async/AsyncStatementResultCursorImplTest.java index cc051a19eb..8c11556974 100644 --- a/driver/src/test/java/org/neo4j/driver/internal/async/AsyncStatementResultCursorTest.java +++ b/driver/src/test/java/org/neo4j/driver/internal/async/AsyncStatementResultCursorImplTest.java @@ -33,6 +33,7 @@ import org.neo4j.driver.exceptions.ServiceUnavailableException; import org.neo4j.driver.internal.BoltServerAddress; import org.neo4j.driver.internal.InternalRecord; +import org.neo4j.driver.internal.cursor.AsyncStatementResultCursorImpl; import org.neo4j.driver.internal.handlers.PullAllResponseHandler; import org.neo4j.driver.internal.handlers.RunResponseHandler; import org.neo4j.driver.internal.messaging.v1.BoltProtocolV1; @@ -64,7 +65,7 @@ import static org.neo4j.driver.util.TestUtil.anyServerVersion; import static org.neo4j.driver.util.TestUtil.await; -class AsyncStatementResultCursorTest +class AsyncStatementResultCursorImplTest { @Test void shouldReturnStatementKeys() @@ -75,7 +76,7 @@ void shouldReturnStatementKeys() List keys = asList( "key1", "key2", "key3" ); runHandler.onSuccess( singletonMap( "fields", value( keys ) ) ); - AsyncStatementResultCursor cursor = newCursor( runHandler, pullAllHandler ); + AsyncStatementResultCursorImpl cursor = newCursor( runHandler, pullAllHandler ); assertEquals( keys, cursor.keys() ); } @@ -90,7 +91,7 @@ void shouldReturnSummary() new InternalSummaryCounters( 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11 ), null, null, emptyList(), 42, 42 ); when( pullAllHandler.summaryAsync() ).thenReturn( completedFuture( summary ) ); - AsyncStatementResultCursor cursor = newCursor( pullAllHandler ); + AsyncStatementResultCursorImpl cursor = newCursor( pullAllHandler ); assertEquals( summary, await( cursor.summaryAsync() ) ); } @@ -103,7 +104,7 @@ void shouldReturnNextExistingRecord() Record record = new InternalRecord( asList( "key1", "key2" ), values( 1, 2 ) ); when( pullAllHandler.nextAsync() ).thenReturn( completedFuture( record ) ); - AsyncStatementResultCursor cursor = newCursor( pullAllHandler ); + AsyncStatementResultCursorImpl cursor = newCursor( pullAllHandler ); assertEquals( record, await( cursor.nextAsync() ) ); } @@ -114,7 +115,7 @@ void shouldReturnNextNonExistingRecord() PullAllResponseHandler pullAllHandler = mock( PullAllResponseHandler.class ); when( pullAllHandler.nextAsync() ).thenReturn( completedWithNull() ); - AsyncStatementResultCursor cursor = newCursor( pullAllHandler ); + AsyncStatementResultCursorImpl cursor = newCursor( pullAllHandler ); assertNull( await( cursor.nextAsync() ) ); } @@ -127,7 +128,7 @@ void shouldPeekExistingRecord() Record record = new InternalRecord( asList( "key1", "key2", "key3" ), values( 3, 2, 1 ) ); when( pullAllHandler.peekAsync() ).thenReturn( completedFuture( record ) ); - AsyncStatementResultCursor cursor = newCursor( pullAllHandler ); + AsyncStatementResultCursorImpl cursor = newCursor( pullAllHandler ); assertEquals( record, await( cursor.peekAsync() ) ); } @@ -138,7 +139,7 @@ void shouldPeekNonExistingRecord() PullAllResponseHandler pullAllHandler = mock( PullAllResponseHandler.class ); when( pullAllHandler.peekAsync() ).thenReturn( completedWithNull() ); - AsyncStatementResultCursor cursor = newCursor( pullAllHandler ); + AsyncStatementResultCursorImpl cursor = newCursor( pullAllHandler ); assertNull( await( cursor.peekAsync() ) ); } @@ -152,7 +153,7 @@ void shouldReturnSingleRecord() when( pullAllHandler.nextAsync() ).thenReturn( completedFuture( record ) ) .thenReturn( completedWithNull() ); - AsyncStatementResultCursor cursor = newCursor( pullAllHandler ); + AsyncStatementResultCursorImpl cursor = newCursor( pullAllHandler ); assertEquals( record, await( cursor.singleAsync() ) ); } @@ -163,7 +164,7 @@ void shouldFailWhenAskedForSingleRecordButResultIsEmpty() PullAllResponseHandler pullAllHandler = mock( PullAllResponseHandler.class ); when( pullAllHandler.nextAsync() ).thenReturn( completedWithNull() ); - AsyncStatementResultCursor cursor = newCursor( pullAllHandler ); + AsyncStatementResultCursorImpl cursor = newCursor( pullAllHandler ); NoSuchRecordException e = assertThrows( NoSuchRecordException.class, () -> await( cursor.singleAsync() ) ); assertThat( e.getMessage(), containsString( "result is empty" ) ); @@ -179,7 +180,7 @@ void shouldFailWhenAskedForSingleRecordButResultContainsMore() when( pullAllHandler.nextAsync() ).thenReturn( completedFuture( record1 ) ) .thenReturn( completedFuture( record2 ) ); - AsyncStatementResultCursor cursor = newCursor( pullAllHandler ); + AsyncStatementResultCursorImpl cursor = newCursor( pullAllHandler ); NoSuchRecordException e = assertThrows( NoSuchRecordException.class, () -> await( cursor.singleAsync() ) ); assertThat( e.getMessage(), containsString( "Ensure your query returns only one record" ) ); @@ -200,7 +201,7 @@ void shouldForEachAsyncWhenResultContainsMultipleRecords() ResultSummary summary = mock( ResultSummary.class ); when( pullAllHandler.summaryAsync() ).thenReturn( completedFuture( summary ) ); - AsyncStatementResultCursor cursor = newCursor( pullAllHandler ); + AsyncStatementResultCursorImpl cursor = newCursor( pullAllHandler ); List records = new CopyOnWriteArrayList<>(); CompletionStage summaryStage = cursor.forEachAsync( records::add ); @@ -221,7 +222,7 @@ void shouldForEachAsyncWhenResultContainsOneRecords() ResultSummary summary = mock( ResultSummary.class ); when( pullAllHandler.summaryAsync() ).thenReturn( completedFuture( summary ) ); - AsyncStatementResultCursor cursor = newCursor( pullAllHandler ); + AsyncStatementResultCursorImpl cursor = newCursor( pullAllHandler ); List records = new CopyOnWriteArrayList<>(); CompletionStage summaryStage = cursor.forEachAsync( records::add ); @@ -239,7 +240,7 @@ void shouldForEachAsyncWhenResultContainsNoRecords() ResultSummary summary = mock( ResultSummary.class ); when( pullAllHandler.summaryAsync() ).thenReturn( completedFuture( summary ) ); - AsyncStatementResultCursor cursor = newCursor( pullAllHandler ); + AsyncStatementResultCursorImpl cursor = newCursor( pullAllHandler ); List records = new CopyOnWriteArrayList<>(); CompletionStage summaryStage = cursor.forEachAsync( records::add ); @@ -260,7 +261,7 @@ void shouldFailForEachWhenGivenActionThrows() .thenReturn( completedFuture( record2 ) ).thenReturn( completedFuture( record3 ) ) .thenReturn( completedWithNull() ); - AsyncStatementResultCursor cursor = newCursor( pullAllHandler ); + AsyncStatementResultCursorImpl cursor = newCursor( pullAllHandler ); AtomicInteger recordsProcessed = new AtomicInteger(); RuntimeException error = new RuntimeException( "Hello" ); @@ -292,7 +293,7 @@ void shouldReturnFailureWhenExists() ServiceUnavailableException error = new ServiceUnavailableException( "Hi" ); when( pullAllHandler.failureAsync() ).thenReturn( completedFuture( error ) ); - AsyncStatementResultCursor cursor = newCursor( pullAllHandler ); + AsyncStatementResultCursorImpl cursor = newCursor( pullAllHandler ); assertEquals( error, await( cursor.failureAsync() ) ); } @@ -303,7 +304,7 @@ void shouldReturnNullFailureWhenDoesNotExist() PullAllResponseHandler pullAllHandler = mock( PullAllResponseHandler.class ); when( pullAllHandler.failureAsync() ).thenReturn( completedWithNull() ); - AsyncStatementResultCursor cursor = newCursor( pullAllHandler ); + AsyncStatementResultCursorImpl cursor = newCursor( pullAllHandler ); assertNull( await( cursor.failureAsync() ) ); } @@ -319,7 +320,7 @@ void shouldListAsyncWithoutMapFunction() when( pullAllHandler.listAsync( Function.identity() ) ).thenReturn( completedFuture( records ) ); - AsyncStatementResultCursor cursor = newCursor( pullAllHandler ); + AsyncStatementResultCursorImpl cursor = newCursor( pullAllHandler ); assertEquals( records, await( cursor.listAsync() ) ); verify( pullAllHandler ).listAsync( Function.identity() ); @@ -334,7 +335,7 @@ void shouldListAsyncWithMapFunction() List values = asList( "a", "b", "c", "d", "e" ); when( pullAllHandler.listAsync( mapFunction ) ).thenReturn( completedFuture( values ) ); - AsyncStatementResultCursor cursor = newCursor( pullAllHandler ); + AsyncStatementResultCursorImpl cursor = newCursor( pullAllHandler ); assertEquals( values, await( cursor.listAsync( mapFunction ) ) ); verify( pullAllHandler ).listAsync( mapFunction ); @@ -347,7 +348,7 @@ void shouldPropagateFailureFromListAsyncWithoutMapFunction() RuntimeException error = new RuntimeException( "Hi" ); when( pullAllHandler.listAsync( Function.identity() ) ).thenReturn( failedFuture( error ) ); - AsyncStatementResultCursor cursor = newCursor( pullAllHandler ); + AsyncStatementResultCursorImpl cursor = newCursor( pullAllHandler ); RuntimeException e = assertThrows( RuntimeException.class, () -> await( cursor.listAsync() ) ); assertEquals( error, e ); @@ -362,7 +363,7 @@ void shouldPropagateFailureFromListAsyncWithMapFunction() RuntimeException error = new RuntimeException( "Hi" ); when( pullAllHandler.listAsync( mapFunction ) ).thenReturn( failedFuture( error ) ); - AsyncStatementResultCursor cursor = newCursor( pullAllHandler ); + AsyncStatementResultCursorImpl cursor = newCursor( pullAllHandler ); RuntimeException e = assertThrows( RuntimeException.class, () -> await( cursor.listAsync( mapFunction ) ) ); assertEquals( error, e ); @@ -375,11 +376,11 @@ void shouldConsumeAsync() { PullAllResponseHandler pullAllHandler = mock( PullAllResponseHandler.class ); ResultSummary summary = mock( ResultSummary.class ); - when( pullAllHandler.consumeAsync() ).thenReturn( completedFuture( summary ) ); + when( pullAllHandler.summaryAsync() ).thenReturn( completedFuture( summary ) ); - AsyncStatementResultCursor cursor = newCursor( pullAllHandler ); + AsyncStatementResultCursorImpl cursor = newCursor( pullAllHandler ); - assertEquals( summary, await( cursor.consumeAsync() ) ); + assertEquals( summary, await( cursor.summaryAsync() ) ); } @Test @@ -387,22 +388,22 @@ void shouldPropagateFailureInConsumeAsync() { PullAllResponseHandler pullAllHandler = mock( PullAllResponseHandler.class ); RuntimeException error = new RuntimeException( "Hi" ); - when( pullAllHandler.consumeAsync() ).thenReturn( failedFuture( error ) ); + when( pullAllHandler.summaryAsync() ).thenReturn( failedFuture( error ) ); - AsyncStatementResultCursor cursor = newCursor( pullAllHandler ); + AsyncStatementResultCursorImpl cursor = newCursor( pullAllHandler ); - RuntimeException e = assertThrows( RuntimeException.class, () -> await( cursor.consumeAsync() ) ); + RuntimeException e = assertThrows( RuntimeException.class, () -> await( cursor.summaryAsync() ) ); assertEquals( error, e ); } - private static AsyncStatementResultCursor newCursor( PullAllResponseHandler pullAllHandler ) + private static AsyncStatementResultCursorImpl newCursor( PullAllResponseHandler pullAllHandler ) { - return new AsyncStatementResultCursor( newRunResponseHandler(), pullAllHandler ); + return new AsyncStatementResultCursorImpl( newRunResponseHandler(), pullAllHandler ); } - private static AsyncStatementResultCursor newCursor( RunResponseHandler runHandler, PullAllResponseHandler pullAllHandler ) + private static AsyncStatementResultCursorImpl newCursor( RunResponseHandler runHandler, PullAllResponseHandler pullAllHandler ) { - return new AsyncStatementResultCursor( runHandler, pullAllHandler ); + return new AsyncStatementResultCursorImpl( runHandler, pullAllHandler ); } private static RunResponseHandler newRunResponseHandler() diff --git a/driver/src/test/java/org/neo4j/driver/internal/async/ExplicitTransactionTest.java b/driver/src/test/java/org/neo4j/driver/internal/async/ExplicitTransactionTest.java index e6770e985c..fe94f207fc 100644 --- a/driver/src/test/java/org/neo4j/driver/internal/async/ExplicitTransactionTest.java +++ b/driver/src/test/java/org/neo4j/driver/internal/async/ExplicitTransactionTest.java @@ -48,12 +48,13 @@ import static org.mockito.Mockito.inOrder; import static org.mockito.Mockito.never; import static org.mockito.Mockito.verify; +import static org.neo4j.driver.internal.handlers.pulln.FetchSizeUtil.UNLIMITED_FETCH_SIZE; import static org.neo4j.driver.util.TestUtil.await; import static org.neo4j.driver.util.TestUtil.connectionMock; import static org.neo4j.driver.util.TestUtil.runMessageWithStatementMatcher; -import static org.neo4j.driver.util.TestUtil.setupSuccessfulRun; +import static org.neo4j.driver.util.TestUtil.setupSuccessfulRunRx; import static org.neo4j.driver.util.TestUtil.setupSuccessfulRunAndPull; -import static org.neo4j.driver.util.TestUtil.verifyRun; +import static org.neo4j.driver.util.TestUtil.verifyRunRx; import static org.neo4j.driver.util.TestUtil.verifyRunAndPull; class ExplicitTransactionTest @@ -80,13 +81,13 @@ void shouldFlushOnRunRx() // Given Connection connection = connectionMock( BoltProtocolV4.INSTANCE ); ExplicitTransaction tx = beginTx( connection ); - setupSuccessfulRun( connection ); + setupSuccessfulRunRx( connection ); // When await( tx.runRx( new Statement( "RETURN 1" ) ) ); // Then - verifyRun( connection, "RETURN 1" ); + verifyRunRx( connection, "RETURN 1" ); } @Test @@ -163,7 +164,7 @@ void shouldReleaseConnectionWhenBeginFails() { RuntimeException error = new RuntimeException( "Wrong bookmark!" ); Connection connection = connectionWithBegin( handler -> handler.onFailure( error ) ); - ExplicitTransaction tx = new ExplicitTransaction( connection, new DefaultBookmarkHolder() ); + ExplicitTransaction tx = new ExplicitTransaction( connection, new DefaultBookmarkHolder(), UNLIMITED_FETCH_SIZE ); InternalBookmark bookmark = InternalBookmark.parse( "SomeBookmark" ); TransactionConfig txConfig = TransactionConfig.empty(); @@ -178,7 +179,7 @@ void shouldReleaseConnectionWhenBeginFails() void shouldNotReleaseConnectionWhenBeginSucceeds() { Connection connection = connectionWithBegin( handler -> handler.onSuccess( emptyMap() ) ); - ExplicitTransaction tx = new ExplicitTransaction( connection, new DefaultBookmarkHolder() ); + ExplicitTransaction tx = new ExplicitTransaction( connection, new DefaultBookmarkHolder(), UNLIMITED_FETCH_SIZE ); InternalBookmark bookmark = InternalBookmark.parse( "SomeBookmark" ); TransactionConfig txConfig = TransactionConfig.empty(); @@ -192,7 +193,7 @@ void shouldNotReleaseConnectionWhenBeginSucceeds() void shouldReleaseConnectionWhenTerminatedAndCommitted() { Connection connection = connectionMock(); - ExplicitTransaction tx = new ExplicitTransaction( connection, new DefaultBookmarkHolder() ); + ExplicitTransaction tx = new ExplicitTransaction( connection, new DefaultBookmarkHolder(), UNLIMITED_FETCH_SIZE ); tx.markTerminated(); @@ -206,7 +207,7 @@ void shouldReleaseConnectionWhenTerminatedAndCommitted() void shouldReleaseConnectionWhenTerminatedAndRolledBack() { Connection connection = connectionMock(); - ExplicitTransaction tx = new ExplicitTransaction( connection, new DefaultBookmarkHolder() ); + ExplicitTransaction tx = new ExplicitTransaction( connection, new DefaultBookmarkHolder(), UNLIMITED_FETCH_SIZE ); tx.markTerminated(); await( tx.rollbackAsync() ); @@ -218,7 +219,7 @@ void shouldReleaseConnectionWhenTerminatedAndRolledBack() void shouldReleaseConnectionWhenClose() throws Throwable { Connection connection = connectionMock(); - ExplicitTransaction tx = new ExplicitTransaction( connection, new DefaultBookmarkHolder() ); + ExplicitTransaction tx = new ExplicitTransaction( connection, new DefaultBookmarkHolder(), UNLIMITED_FETCH_SIZE ); await( tx.closeAsync() ); @@ -232,7 +233,7 @@ private static ExplicitTransaction beginTx( Connection connection ) private static ExplicitTransaction beginTx( Connection connection, InternalBookmark initialBookmark ) { - ExplicitTransaction tx = new ExplicitTransaction( connection, new DefaultBookmarkHolder() ); + ExplicitTransaction tx = new ExplicitTransaction( connection, new DefaultBookmarkHolder(), UNLIMITED_FETCH_SIZE ); return await( tx.beginAsync( initialBookmark, TransactionConfig.empty() ) ); } diff --git a/driver/src/test/java/org/neo4j/driver/internal/async/LeakLoggingNetworkSessionTest.java b/driver/src/test/java/org/neo4j/driver/internal/async/LeakLoggingNetworkSessionTest.java index df974a62cd..068694dc80 100644 --- a/driver/src/test/java/org/neo4j/driver/internal/async/LeakLoggingNetworkSessionTest.java +++ b/driver/src/test/java/org/neo4j/driver/internal/async/LeakLoggingNetworkSessionTest.java @@ -28,6 +28,7 @@ import org.neo4j.driver.Logging; import org.neo4j.driver.TransactionConfig; import org.neo4j.driver.internal.DefaultBookmarkHolder; +import org.neo4j.driver.internal.handlers.pulln.FetchSizeUtil; import org.neo4j.driver.internal.spi.Connection; import org.neo4j.driver.internal.spi.ConnectionProvider; import org.neo4j.driver.internal.util.FixedRetryLogic; @@ -97,7 +98,7 @@ private static void finalize( NetworkSession session ) throws Exception private static LeakLoggingNetworkSession newSession( Logging logging, boolean openConnection ) { return new LeakLoggingNetworkSession( connectionProviderMock( openConnection ), new FixedRetryLogic( 0 ), defaultDatabase(), READ, - new DefaultBookmarkHolder(), logging ); + new DefaultBookmarkHolder(), FetchSizeUtil.UNLIMITED_FETCH_SIZE, logging ); } private static ConnectionProvider connectionProviderMock( boolean openConnection ) diff --git a/driver/src/test/java/org/neo4j/driver/internal/async/NetworkSessionTest.java b/driver/src/test/java/org/neo4j/driver/internal/async/NetworkSessionTest.java index 3f9d0efee6..9fc32c3aae 100644 --- a/driver/src/test/java/org/neo4j/driver/internal/async/NetworkSessionTest.java +++ b/driver/src/test/java/org/neo4j/driver/internal/async/NetworkSessionTest.java @@ -65,11 +65,11 @@ import static org.neo4j.driver.util.TestUtil.connectionMock; import static org.neo4j.driver.util.TestUtil.newSession; import static org.neo4j.driver.util.TestUtil.setupFailingBegin; -import static org.neo4j.driver.util.TestUtil.setupSuccessfulRun; +import static org.neo4j.driver.util.TestUtil.setupSuccessfulRunRx; import static org.neo4j.driver.util.TestUtil.setupSuccessfulRunAndPull; import static org.neo4j.driver.util.TestUtil.verifyBeginTx; import static org.neo4j.driver.util.TestUtil.verifyRollbackTx; -import static org.neo4j.driver.util.TestUtil.verifyRun; +import static org.neo4j.driver.util.TestUtil.verifyRunRx; import static org.neo4j.driver.util.TestUtil.verifyRunAndPull; class NetworkSessionTest @@ -101,10 +101,10 @@ void shouldFlushOnRunAsync( boolean waitForResponse ) @Test void shouldFlushOnRunRx() { - setupSuccessfulRun( connection ); + setupSuccessfulRunRx( connection ); await( session.runRx( new Statement( "RETURN 1" ), TransactionConfig.empty() ) ); - verifyRun( connection, "RETURN 1" ); + verifyRunRx( connection, "RETURN 1" ); } @Test @@ -195,7 +195,8 @@ void releasesOpenConnectionUsedForRunWhenSessionIsClosed() close( session ); InOrder inOrder = inOrder( connection ); - inOrder.verify( connection ).writeAndFlush( any( RunWithMetadataMessage.class ), any(), any( PullMessage.class ), any() ); + inOrder.verify( connection ).write( any( RunWithMetadataMessage.class ), any() ); + inOrder.verify( connection ).writeAndFlush( any( PullMessage.class ), any() ); inOrder.verify( connection, atLeastOnce() ).release(); } diff --git a/driver/src/test/java/org/neo4j/driver/internal/async/ResultCursorsHolderTest.java b/driver/src/test/java/org/neo4j/driver/internal/async/ResultCursorsHolderTest.java index 24dfac964a..9ca049d743 100644 --- a/driver/src/test/java/org/neo4j/driver/internal/async/ResultCursorsHolderTest.java +++ b/driver/src/test/java/org/neo4j/driver/internal/async/ResultCursorsHolderTest.java @@ -25,6 +25,7 @@ import java.util.concurrent.CompletionStage; import java.util.concurrent.TimeoutException; +import org.neo4j.driver.internal.cursor.AsyncStatementResultCursorImpl; import org.neo4j.driver.internal.util.Futures; import static java.util.concurrent.CompletableFuture.completedFuture; @@ -135,20 +136,20 @@ void shouldWaitForAllFailuresToArrive() assertEquals( error1, await( failureFuture ) ); } - private static CompletionStage cursorWithoutError() + private static CompletionStage cursorWithoutError() { return cursorWithError( null ); } - private static CompletionStage cursorWithError( Throwable error ) + private static CompletionStage cursorWithError( Throwable error ) { return cursorWithFailureFuture( completedFuture( error ) ); } - private static CompletionStage cursorWithFailureFuture( CompletableFuture future ) + private static CompletionStage cursorWithFailureFuture( CompletableFuture future ) { - AsyncStatementResultCursor cursor = mock( AsyncStatementResultCursor.class ); - when( cursor.failureAsync() ).thenReturn( future ); + AsyncStatementResultCursorImpl cursor = mock( AsyncStatementResultCursorImpl.class ); + when( cursor.consumeAsync() ).thenReturn( future ); return completedFuture( cursor ); } } diff --git a/driver/src/test/java/org/neo4j/driver/internal/cursor/AsyncResultCursorOnlyFactoryTest.java b/driver/src/test/java/org/neo4j/driver/internal/cursor/AsyncStatementResultCursorOnlyFactoryTest.java similarity index 71% rename from driver/src/test/java/org/neo4j/driver/internal/cursor/AsyncResultCursorOnlyFactoryTest.java rename to driver/src/test/java/org/neo4j/driver/internal/cursor/AsyncStatementResultCursorOnlyFactoryTest.java index 7f6df1aaa4..32c8292a79 100644 --- a/driver/src/test/java/org/neo4j/driver/internal/cursor/AsyncResultCursorOnlyFactoryTest.java +++ b/driver/src/test/java/org/neo4j/driver/internal/cursor/AsyncStatementResultCursorOnlyFactoryTest.java @@ -27,9 +27,9 @@ import java.util.concurrent.CompletionStage; import java.util.stream.Stream; -import org.neo4j.driver.internal.async.AsyncStatementResultCursor; -import org.neo4j.driver.internal.handlers.AbstractPullAllResponseHandler; +import org.neo4j.driver.internal.handlers.PullAllResponseHandler; import org.neo4j.driver.internal.handlers.RunResponseHandler; +import org.neo4j.driver.internal.handlers.pulln.AutoPullResponseHandler; import org.neo4j.driver.internal.messaging.Message; import org.neo4j.driver.internal.spi.Connection; @@ -40,16 +40,14 @@ import static org.junit.Assert.assertThat; import static org.junit.jupiter.api.Assertions.assertThrows; import static org.mockito.ArgumentMatchers.any; -import static org.mockito.ArgumentMatchers.eq; import static org.mockito.Mockito.mock; import static org.mockito.Mockito.verify; import static org.mockito.Mockito.when; -import static org.neo4j.driver.internal.messaging.request.PullAllMessage.PULL_ALL; import static org.neo4j.driver.internal.util.Futures.completedWithNull; import static org.neo4j.driver.internal.util.Futures.failedFuture; import static org.neo4j.driver.internal.util.Futures.getNow; -class AsyncResultCursorOnlyFactoryTest +class AsyncStatementResultCursorOnlyFactoryTest { private static Stream waitForRun() { @@ -66,7 +64,7 @@ void shouldReturnAsyncResultWhenRunSucceeded( boolean waitForRun ) throws Throwa StatementResultCursorFactory cursorFactory = newResultCursorFactory( connection, completedWithNull(), waitForRun ); // When - CompletionStage cursorFuture = cursorFactory.asyncResult(); + CompletionStage cursorFuture = cursorFactory.asyncResult(); // Then @@ -83,7 +81,7 @@ void shouldReturnAsyncResultWhenRunCompletedWithFailure( boolean waitForRun ) th StatementResultCursorFactory cursorFactory = newResultCursorFactory( connection, completedFuture( error ), waitForRun ); // When - CompletionStage cursorFuture = cursorFactory.asyncResult(); + CompletionStage cursorFuture = cursorFactory.asyncResult(); // Then verifyRunCompleted( connection, cursorFuture ); @@ -97,7 +95,7 @@ void shouldFailAsyncResultWhenRunFailed() throws Throwable StatementResultCursorFactory cursorFactory = newResultCursorFactory( failedFuture( error ), true ); // When - CompletionStage cursorFuture = cursorFactory.asyncResult(); + CompletionStage cursorFuture = cursorFactory.asyncResult(); // Then CompletionException actual = assertThrows( CompletionException.class, () -> getNow( cursorFuture ) ); @@ -113,12 +111,34 @@ void shouldNotFailAsyncResultEvenWhenRunFailed() throws Throwable StatementResultCursorFactory cursorFactory = newResultCursorFactory( connection, failedFuture( error ), false ); // When - CompletionStage cursorFuture = cursorFactory.asyncResult(); + CompletionStage cursorFuture = cursorFactory.asyncResult(); // Then verifyRunCompleted( connection, cursorFuture ); } + @ParameterizedTest + @MethodSource( "waitForRun" ) + void shouldPrePopulateRecords( boolean waitForRun ) throws Throwable + { + // Given + Connection connection = mock( Connection.class ); + Message runMessage = mock( Message.class ); + + RunResponseHandler runHandler = mock( RunResponseHandler.class ); + when( runHandler.runFuture() ).thenReturn( completedWithNull() ); + + PullAllResponseHandler pullAllHandler = mock( PullAllResponseHandler.class ); + + StatementResultCursorFactory cursorFactory = new AsyncStatementResultCursorOnlyFactory( connection, runMessage, runHandler, pullAllHandler, waitForRun ); + + // When + cursorFactory.asyncResult(); + + // Then + verify( pullAllHandler ).prePopulateRecords(); + } + // rxResult @ParameterizedTest @MethodSource( "waitForRun" ) @@ -133,28 +153,27 @@ void shouldErrorForRxResult( boolean waitForRun ) throws Throwable assertThat( error.getCause().getMessage(), containsString( "Driver is connected to the database that does not support driver reactive API" ) ); } - private AsyncResultCursorOnlyFactory newResultCursorFactory( Connection connection, CompletableFuture runFuture, boolean waitForRun ) + private AsyncStatementResultCursorOnlyFactory newResultCursorFactory( Connection connection, CompletableFuture runFuture, boolean waitForRun ) { Message runMessage = mock( Message.class ); RunResponseHandler runHandler = mock( RunResponseHandler.class ); when( runHandler.runFuture() ).thenReturn( runFuture ); - AbstractPullAllResponseHandler pullHandler = mock( AbstractPullAllResponseHandler.class ); + AutoPullResponseHandler pullHandler = mock( AutoPullResponseHandler.class ); - return new AsyncResultCursorOnlyFactory( connection, runMessage, runHandler, pullHandler, waitForRun ); + return new AsyncStatementResultCursorOnlyFactory( connection, runMessage, runHandler, pullHandler, waitForRun ); } - private AsyncResultCursorOnlyFactory newResultCursorFactory( CompletableFuture runFuture, boolean waitForRun ) + private AsyncStatementResultCursorOnlyFactory newResultCursorFactory( CompletableFuture runFuture, boolean waitForRun ) { Connection connection = mock( Connection.class ); return newResultCursorFactory( connection, runFuture, waitForRun ); } - private void verifyRunCompleted( Connection connection, CompletionStage cursorFuture ) + private void verifyRunCompleted( Connection connection, CompletionStage cursorFuture ) { - verify( connection ).writeAndFlush( any( Message.class ), any( RunResponseHandler.class ), eq( PULL_ALL ), - any( AbstractPullAllResponseHandler.class ) ); - assertThat( getNow( cursorFuture ), instanceOf( AsyncStatementResultCursor.class ) ); + verify( connection ).write( any( Message.class ), any( RunResponseHandler.class ) ); + assertThat( getNow( cursorFuture ), instanceOf( AsyncStatementResultCursorImpl.class ) ); } } diff --git a/driver/src/test/java/org/neo4j/driver/internal/cursor/RxStatementResultCursorTest.java b/driver/src/test/java/org/neo4j/driver/internal/cursor/RxStatementResultCursorImplTest.java similarity index 82% rename from driver/src/test/java/org/neo4j/driver/internal/cursor/RxStatementResultCursorTest.java rename to driver/src/test/java/org/neo4j/driver/internal/cursor/RxStatementResultCursorImplTest.java index a302781a51..7240e5ac8c 100644 --- a/driver/src/test/java/org/neo4j/driver/internal/cursor/RxStatementResultCursorTest.java +++ b/driver/src/test/java/org/neo4j/driver/internal/cursor/RxStatementResultCursorImplTest.java @@ -26,7 +26,7 @@ import java.util.function.BiConsumer; import org.neo4j.driver.internal.handlers.RunResponseHandler; -import org.neo4j.driver.internal.handlers.pulln.BasicPullResponseHandler; +import org.neo4j.driver.internal.handlers.pulln.PullResponseHandler; import org.neo4j.driver.internal.reactive.util.ListBasedPullHandler; import static java.util.Arrays.asList; @@ -41,12 +41,12 @@ import static org.mockito.Mockito.mock; import static org.mockito.Mockito.verify; import static org.mockito.Mockito.verifyNoMoreInteractions; -import static org.neo4j.driver.internal.handlers.pulln.AbstractBasicPullResponseHandler.DISCARD_RECORD_CONSUMER; +import static org.neo4j.driver.Values.value; +import static org.neo4j.driver.internal.cursor.RxStatementResultCursorImpl.DISCARD_RECORD_CONSUMER; import static org.neo4j.driver.internal.messaging.v3.BoltProtocolV3.METADATA_EXTRACTOR; import static org.neo4j.driver.internal.util.Futures.completedWithNull; -import static org.neo4j.driver.Values.value; -class RxStatementResultCursorTest +class RxStatementResultCursorImplTest { @Test void shouldWaitForRunToFinishBeforeCreatingRxResultCurosr() throws Throwable @@ -54,10 +54,10 @@ void shouldWaitForRunToFinishBeforeCreatingRxResultCurosr() throws Throwable // Given CompletableFuture runFuture = new CompletableFuture<>(); RunResponseHandler runHandler = newRunResponseHandler( runFuture ); - BasicPullResponseHandler pullHandler = mock( BasicPullResponseHandler.class ); + PullResponseHandler pullHandler = mock( PullResponseHandler.class ); // When - IllegalStateException error = assertThrows( IllegalStateException.class, () -> new RxStatementResultCursor( runHandler, pullHandler ) ); + IllegalStateException error = assertThrows( IllegalStateException.class, () -> new RxStatementResultCursorImpl( runHandler, pullHandler ) ); // Then assertThat( error.getMessage(), containsString( "Should wait for response of RUN" ) ); } @@ -68,10 +68,10 @@ void shouldInstallSummaryConsumerWithoutReportingError() throws Throwable // Given RuntimeException error = new RuntimeException( "Hi" ); RunResponseHandler runHandler = newRunResponseHandler( error ); - BasicPullResponseHandler pullHandler = mock( BasicPullResponseHandler.class ); + PullResponseHandler pullHandler = mock( PullResponseHandler.class ); // When - new RxStatementResultCursor( error, runHandler, pullHandler ); + new RxStatementResultCursorImpl( error, runHandler, pullHandler ); // Then verify( pullHandler ).installSummaryConsumer( any( BiConsumer.class ) ); @@ -86,10 +86,10 @@ void shouldReturnStatementKeys() throws Throwable List expected = asList( "key1", "key2", "key3" ); runHandler.onSuccess( Collections.singletonMap( "fields", value( expected ) ) ); - BasicPullResponseHandler pullHandler = mock( BasicPullResponseHandler.class ); + PullResponseHandler pullHandler = mock( PullResponseHandler.class ); // When - RxStatementResultCursor cursor = new RxStatementResultCursor( runHandler, pullHandler ); + RxStatementResultCursor cursor = new RxStatementResultCursorImpl( runHandler, pullHandler ); List actual = cursor.keys(); // Then @@ -104,10 +104,10 @@ void shouldSupportReturnStatementKeysMultipleTimes() throws Throwable List expected = asList( "key1", "key2", "key3" ); runHandler.onSuccess( Collections.singletonMap( "fields", value( expected ) ) ); - BasicPullResponseHandler pullHandler = mock( BasicPullResponseHandler.class ); + PullResponseHandler pullHandler = mock( PullResponseHandler.class ); // When - RxStatementResultCursor cursor = new RxStatementResultCursor( runHandler, pullHandler ); + RxStatementResultCursor cursor = new RxStatementResultCursorImpl( runHandler, pullHandler ); // Then List actual = cursor.keys(); @@ -126,8 +126,8 @@ void shouldPull() throws Throwable { // Given RunResponseHandler runHandler = newRunResponseHandler(); - BasicPullResponseHandler pullHandler = mock( BasicPullResponseHandler.class ); - RxStatementResultCursor cursor = new RxStatementResultCursor( runHandler, pullHandler ); + PullResponseHandler pullHandler = mock( PullResponseHandler.class ); + RxStatementResultCursor cursor = new RxStatementResultCursorImpl( runHandler, pullHandler ); // When cursor.request( 100 ); @@ -141,8 +141,8 @@ void shouldCancel() throws Throwable { // Given RunResponseHandler runHandler = newRunResponseHandler(); - BasicPullResponseHandler pullHandler = mock( BasicPullResponseHandler.class ); - RxStatementResultCursor cursor = new RxStatementResultCursor( runHandler, pullHandler ); + PullResponseHandler pullHandler = mock( PullResponseHandler.class ); + RxStatementResultCursor cursor = new RxStatementResultCursorImpl( runHandler, pullHandler ); // When cursor.cancel(); @@ -160,8 +160,8 @@ void shouldInstallRecordConsumerAndReportError() throws Throwable // When RunResponseHandler runHandler = newRunResponseHandler( error ); - BasicPullResponseHandler pullHandler = new ListBasedPullHandler(); - RxStatementResultCursor cursor = new RxStatementResultCursor( error, runHandler, pullHandler ); + PullResponseHandler pullHandler = new ListBasedPullHandler(); + RxStatementResultCursor cursor = new RxStatementResultCursorImpl( error, runHandler, pullHandler ); cursor.installRecordConsumer( recordConsumer ); // Then @@ -174,8 +174,8 @@ void shouldReturnSummaryFuture() throws Throwable { // Given RunResponseHandler runHandler = newRunResponseHandler(); - BasicPullResponseHandler pullHandler = new ListBasedPullHandler(); - RxStatementResultCursor cursor = new RxStatementResultCursor( runHandler, pullHandler ); + PullResponseHandler pullHandler = new ListBasedPullHandler(); + RxStatementResultCursor cursor = new RxStatementResultCursorImpl( runHandler, pullHandler ); // When cursor.installRecordConsumer( DISCARD_RECORD_CONSUMER ); @@ -191,8 +191,8 @@ void shouldCancelIfNotPulled() throws Throwable { // Given RunResponseHandler runHandler = newRunResponseHandler(); - BasicPullResponseHandler pullHandler = mock( BasicPullResponseHandler.class ); - RxStatementResultCursor cursor = new RxStatementResultCursor( runHandler, pullHandler ); + PullResponseHandler pullHandler = mock( PullResponseHandler.class ); + RxStatementResultCursor cursor = new RxStatementResultCursorImpl( runHandler, pullHandler ); // When cursor.summaryAsync(); diff --git a/driver/src/test/java/org/neo4j/driver/internal/cursor/InternalStatementResultCursorFactoryTest.java b/driver/src/test/java/org/neo4j/driver/internal/cursor/StatementResultCursorFactoryImplTest.java similarity index 76% rename from driver/src/test/java/org/neo4j/driver/internal/cursor/InternalStatementResultCursorFactoryTest.java rename to driver/src/test/java/org/neo4j/driver/internal/cursor/StatementResultCursorFactoryImplTest.java index 0bf373d570..490906fc5f 100644 --- a/driver/src/test/java/org/neo4j/driver/internal/cursor/InternalStatementResultCursorFactoryTest.java +++ b/driver/src/test/java/org/neo4j/driver/internal/cursor/StatementResultCursorFactoryImplTest.java @@ -27,10 +27,9 @@ import java.util.concurrent.CompletionStage; import java.util.stream.Stream; -import org.neo4j.driver.internal.async.AsyncStatementResultCursor; import org.neo4j.driver.internal.handlers.PullAllResponseHandler; import org.neo4j.driver.internal.handlers.RunResponseHandler; -import org.neo4j.driver.internal.handlers.pulln.BasicPullResponseHandler; +import org.neo4j.driver.internal.handlers.pulln.PullResponseHandler; import org.neo4j.driver.internal.messaging.Message; import org.neo4j.driver.internal.spi.Connection; @@ -42,12 +41,13 @@ import static org.mockito.ArgumentMatchers.any; import static org.mockito.Mockito.mock; import static org.mockito.Mockito.verify; +import static org.mockito.Mockito.verifyNoMoreInteractions; import static org.mockito.Mockito.when; import static org.neo4j.driver.internal.util.Futures.completedWithNull; import static org.neo4j.driver.internal.util.Futures.failedFuture; import static org.neo4j.driver.internal.util.Futures.getNow; -class InternalStatementResultCursorFactoryTest +class StatementResultCursorFactoryImplTest { private static Stream waitForRun() { @@ -64,7 +64,7 @@ void shouldReturnAsyncResultWhenRunSucceeded( boolean waitForRun ) throws Throwa StatementResultCursorFactory cursorFactory = newResultCursorFactory( connection, completedWithNull(), waitForRun ); // When - CompletionStage cursorFuture = cursorFactory.asyncResult(); + CompletionStage cursorFuture = cursorFactory.asyncResult(); // Then verifyRunCompleted( connection, cursorFuture ); @@ -80,7 +80,7 @@ void shouldReturnAsyncResultWhenRunCompletedWithFailure( boolean waitForRun ) th StatementResultCursorFactory cursorFactory = newResultCursorFactory( connection, completedFuture( error ), waitForRun ); // When - CompletionStage cursorFuture = cursorFactory.asyncResult(); + CompletionStage cursorFuture = cursorFactory.asyncResult(); // Then verifyRunCompleted( connection, cursorFuture ); @@ -94,7 +94,7 @@ void shouldFailAsyncResultWhenRunFailed() throws Throwable StatementResultCursorFactory cursorFactory = newResultCursorFactory( failedFuture( error ), true ); // When - CompletionStage cursorFuture = cursorFactory.asyncResult(); + CompletionStage cursorFuture = cursorFactory.asyncResult(); // Then CompletionException actual = assertThrows( CompletionException.class, () -> getNow( cursorFuture ) ); @@ -110,12 +110,36 @@ void shouldNotFailAsyncResultEvenWhenRunFailed() throws Throwable StatementResultCursorFactory cursorFactory = newResultCursorFactory( connection, failedFuture( error ), false ); // When - CompletionStage cursorFuture = cursorFactory.asyncResult(); + CompletionStage cursorFuture = cursorFactory.asyncResult(); // Then verifyRunCompleted( connection, cursorFuture ); } + @ParameterizedTest + @MethodSource( "waitForRun" ) + void shouldPrePopulateRecords( boolean waitForRun ) throws Throwable + { + // Given + Connection connection = mock( Connection.class ); + Message runMessage = mock( Message.class ); + + RunResponseHandler runHandler = mock( RunResponseHandler.class ); + when( runHandler.runFuture() ).thenReturn( completedWithNull() ); + + PullResponseHandler pullHandler = mock( PullResponseHandler.class ); + PullAllResponseHandler pullAllHandler = mock( PullAllResponseHandler.class ); + + StatementResultCursorFactory cursorFactory = new StatementResultCursorFactoryImpl( connection, runMessage, runHandler, pullHandler, pullAllHandler, waitForRun ); + + // When + cursorFactory.asyncResult(); + + // Then + verify( pullAllHandler ).prePopulateRecords(); + verifyNoMoreInteractions( pullHandler ); + } + // rxResult @ParameterizedTest @MethodSource( "waitForRun" ) @@ -176,34 +200,34 @@ void shouldNotFailRxResultEvenWhenRunFailed() throws Throwable verifyRxRunCompleted( connection, cursorFuture ); } - private InternalStatementResultCursorFactory newResultCursorFactory( Connection connection, CompletableFuture runFuture, boolean waitForRun ) + private StatementResultCursorFactoryImpl newResultCursorFactory( Connection connection, CompletableFuture runFuture, boolean waitForRun ) { Message runMessage = mock( Message.class ); RunResponseHandler runHandler = mock( RunResponseHandler.class ); when( runHandler.runFuture() ).thenReturn( runFuture ); - BasicPullResponseHandler pullHandler = mock( BasicPullResponseHandler.class ); + PullResponseHandler pullHandler = mock( PullResponseHandler.class ); PullAllResponseHandler pullAllHandler = mock( PullAllResponseHandler.class ); - return new InternalStatementResultCursorFactory( connection, runMessage, runHandler, pullHandler, pullAllHandler, waitForRun ); + return new StatementResultCursorFactoryImpl( connection, runMessage, runHandler, pullHandler, pullAllHandler, waitForRun ); } - private InternalStatementResultCursorFactory newResultCursorFactory( CompletableFuture runFuture, boolean waitForRun ) + private StatementResultCursorFactoryImpl newResultCursorFactory( CompletableFuture runFuture, boolean waitForRun ) { Connection connection = mock( Connection.class ); return newResultCursorFactory( connection, runFuture, waitForRun ); } - private void verifyRunCompleted( Connection connection, CompletionStage cursorFuture ) + private void verifyRunCompleted( Connection connection, CompletionStage cursorFuture ) { - verify( connection ).writeAndFlush( any( Message.class ), any( RunResponseHandler.class ), any( Message.class ), any( PullAllResponseHandler.class ) ); - assertThat( getNow( cursorFuture ), instanceOf( AsyncStatementResultCursor.class ) ); + verify( connection ).write( any( Message.class ), any( RunResponseHandler.class ) ); + assertThat( getNow( cursorFuture ), instanceOf( AsyncStatementResultCursorImpl.class ) ); } private void verifyRxRunCompleted( Connection connection, CompletionStage cursorFuture ) { verify( connection ).writeAndFlush( any( Message.class ), any( RunResponseHandler.class ) ); - assertThat( getNow( cursorFuture ), instanceOf( RxStatementResultCursor.class ) ); + assertThat( getNow( cursorFuture ), instanceOf( RxStatementResultCursorImpl.class ) ); } } 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 new file mode 100644 index 0000000000..9223fe23bf --- /dev/null +++ b/driver/src/test/java/org/neo4j/driver/internal/handlers/LegacyPullAllResponseHandlerTest.java @@ -0,0 +1,244 @@ +/* + * 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.handlers; + +import org.junit.jupiter.api.Test; + +import java.util.List; +import java.util.concurrent.CompletableFuture; +import java.util.function.Function; + +import org.neo4j.driver.Record; +import org.neo4j.driver.Statement; +import org.neo4j.driver.internal.spi.Connection; +import org.neo4j.driver.summary.ResultSummary; + +import static java.util.Arrays.asList; +import static java.util.Collections.emptyList; +import static java.util.Collections.emptyMap; +import static java.util.Collections.singletonMap; +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertFalse; +import static org.junit.jupiter.api.Assertions.assertNotNull; +import static org.junit.jupiter.api.Assertions.assertNull; +import static org.junit.jupiter.api.Assertions.assertTrue; +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.never; +import static org.mockito.Mockito.verify; +import static org.neo4j.driver.Values.value; +import static org.neo4j.driver.Values.values; +import static org.neo4j.driver.internal.messaging.v1.BoltProtocolV1.METADATA_EXTRACTOR; +import static org.neo4j.driver.util.TestUtil.await; + +class LegacyPullAllResponseHandlerTest extends PullAllResponseHandlerTestBase +{ + @Test + void shouldDisableAutoReadWhenTooManyRecordsArrive() + { + Connection connection = connectionMock(); + LegacyPullAllResponseHandler handler = newHandler( asList( "key1", "key2" ), connection ); + + for ( int i = 0; i < LegacyPullAllResponseHandler.RECORD_BUFFER_HIGH_WATERMARK + 1; i++ ) + { + handler.onRecord( values( 100, 200 ) ); + } + + verify( connection ).disableAutoRead(); + } + + @Test + void shouldEnableAutoReadWhenRecordsRetrievedFromBuffer() + { + Connection connection = connectionMock(); + List keys = asList( "key1", "key2" ); + LegacyPullAllResponseHandler handler = newHandler( keys, connection ); + + int i; + for ( i = 0; i < LegacyPullAllResponseHandler.RECORD_BUFFER_HIGH_WATERMARK + 1; i++ ) + { + handler.onRecord( values( 100, 200 ) ); + } + + verify( connection, never() ).enableAutoRead(); + verify( connection ).disableAutoRead(); + + while ( i-- > LegacyPullAllResponseHandler.RECORD_BUFFER_LOW_WATERMARK - 1 ) + { + Record record = await( handler.nextAsync() ); + assertNotNull( record ); + assertEquals( keys, record.keys() ); + assertEquals( 100, record.get( "key1" ).asInt() ); + assertEquals( 200, record.get( "key2" ).asInt() ); + } + verify( connection ).enableAutoRead(); + } + + @Test + void shouldNotDisableAutoReadWhenSummaryRequested() + { + Connection connection = connectionMock(); + List keys = asList( "key1", "key2" ); + LegacyPullAllResponseHandler handler = newHandler( keys, connection ); + + CompletableFuture summaryFuture = handler.summaryAsync().toCompletableFuture(); + assertFalse( summaryFuture.isDone() ); + + int recordCount = LegacyPullAllResponseHandler.RECORD_BUFFER_HIGH_WATERMARK + 10; + for ( int i = 0; i < recordCount; i++ ) + { + handler.onRecord( values( "a", "b" ) ); + } + + verify( connection, never() ).disableAutoRead(); + + handler.onSuccess( emptyMap() ); + assertTrue( summaryFuture.isDone() ); + + ResultSummary summary = await( summaryFuture ); + assertNotNull( summary ); + assertNull( await( handler.nextAsync() ) ); + } + + @Test + void shouldNotDisableAutoReadWhenFailureRequested() + { + Connection connection = connectionMock(); + List keys = asList( "key1", "key2" ); + LegacyPullAllResponseHandler handler = newHandler( keys, connection ); + + CompletableFuture failureFuture = handler.failureAsync().toCompletableFuture(); + assertFalse( failureFuture.isDone() ); + + int recordCount = LegacyPullAllResponseHandler.RECORD_BUFFER_HIGH_WATERMARK + 5; + for ( int i = 0; i < recordCount; i++ ) + { + handler.onRecord( values( 123, 456 ) ); + } + + verify( connection, never() ).disableAutoRead(); + + IllegalStateException error = new IllegalStateException( "Wrong config" ); + handler.onFailure( error ); + + assertTrue( failureFuture.isDone() ); + assertEquals( error, await( failureFuture ) ); + + for ( int i = 0; i < recordCount; i++ ) + { + Record record = await( handler.nextAsync() ); + assertNotNull( record ); + assertEquals( keys, record.keys() ); + assertEquals( 123, record.get( "key1" ).asInt() ); + assertEquals( 456, record.get( "key2" ).asInt() ); + } + + assertNull( await( handler.nextAsync() ) ); + } + + @Test + void shouldEnableAutoReadOnConnectionWhenFailureRequestedButNotAvailable() throws Exception + { + Connection connection = connectionMock(); + LegacyPullAllResponseHandler handler = newHandler( asList( "key1", "key2" ), connection ); + + handler.onRecord( values( 1, 2 ) ); + handler.onRecord( values( 3, 4 ) ); + + verify( connection, never() ).enableAutoRead(); + verify( connection, never() ).disableAutoRead(); + + CompletableFuture failureFuture = handler.failureAsync().toCompletableFuture(); + assertFalse( failureFuture.isDone() ); + + verify( connection ).enableAutoRead(); + verify( connection, never() ).disableAutoRead(); + + assertNotNull( await( handler.nextAsync() ) ); + assertNotNull( await( handler.nextAsync() ) ); + + RuntimeException error = new RuntimeException( "Oh my!" ); + handler.onFailure( error ); + + assertTrue( failureFuture.isDone() ); + assertEquals( error, failureFuture.get() ); + } + + @Test + void shouldNotDisableAutoReadWhenAutoReadManagementDisabled() + { + Connection connection = connectionMock(); + LegacyPullAllResponseHandler handler = newHandler( asList( "key1", "key2" ), connection ); + handler.disableAutoReadManagement(); + + for ( int i = 0; i < LegacyPullAllResponseHandler.RECORD_BUFFER_HIGH_WATERMARK + 1; i++ ) + { + handler.onRecord( values( 100, 200 ) ); + } + + verify( connection, never() ).disableAutoRead(); + } + + @Test + void shouldReturnEmptyListInListAsyncAfterFailure() + { + LegacyPullAllResponseHandler handler = newHandler(); + + RuntimeException error = new RuntimeException( "Hi" ); + handler.onFailure( error ); + + // consume the error + assertEquals( error, await( handler.failureAsync() ) ); + assertEquals( emptyList(), await( handler.listAsync( Function.identity() ) ) ); + } + + + @Test + void shouldEnableAutoReadOnConnectionWhenSummaryRequestedButNotAvailable() throws Exception // TODO for auto run + { + Connection connection = connectionMock(); + PullAllResponseHandler handler = newHandler( asList( "key1", "key2", "key3" ), connection ); + + handler.onRecord( values( 1, 2, 3 ) ); + handler.onRecord( values( 4, 5, 6 ) ); + + verify( connection, never() ).enableAutoRead(); + verify( connection, never() ).disableAutoRead(); + + CompletableFuture summaryFuture = handler.summaryAsync().toCompletableFuture(); + assertFalse( summaryFuture.isDone() ); + + verify( connection ).enableAutoRead(); + verify( connection, never() ).disableAutoRead(); + + assertNull( await( handler.nextAsync() ) ); + + handler.onSuccess( emptyMap() ); + + assertTrue( summaryFuture.isDone() ); + assertNotNull( summaryFuture.get() ); + } + + protected LegacyPullAllResponseHandler newHandler( Statement statement, List statementKeys, + Connection connection ) + { + RunResponseHandler runResponseHandler = new RunResponseHandler( new CompletableFuture<>(), METADATA_EXTRACTOR ); + runResponseHandler.onSuccess( singletonMap( "fields", value( statementKeys ) ) ); + return new LegacyPullAllResponseHandler( statement, runResponseHandler, connection, METADATA_EXTRACTOR, mock( PullResponseCompletionListener.class ) ); + } +} diff --git a/driver/src/test/java/org/neo4j/driver/internal/handlers/AbstractPullAllResponseHandlerTest.java b/driver/src/test/java/org/neo4j/driver/internal/handlers/PullAllResponseHandlerTestBase.java similarity index 55% rename from driver/src/test/java/org/neo4j/driver/internal/handlers/AbstractPullAllResponseHandlerTest.java rename to driver/src/test/java/org/neo4j/driver/internal/handlers/PullAllResponseHandlerTestBase.java index fd8ab66ba9..78dfbdba80 100644 --- a/driver/src/test/java/org/neo4j/driver/internal/handlers/AbstractPullAllResponseHandlerTest.java +++ b/driver/src/test/java/org/neo4j/driver/internal/handlers/PullAllResponseHandlerTestBase.java @@ -23,20 +23,18 @@ import java.io.IOException; import java.nio.channels.ClosedChannelException; import java.util.List; -import java.util.Map; import java.util.concurrent.CompletableFuture; import java.util.concurrent.CompletionStage; import java.util.function.Function; -import org.neo4j.driver.internal.BoltServerAddress; -import org.neo4j.driver.internal.InternalRecord; -import org.neo4j.driver.internal.spi.Connection; -import org.neo4j.driver.internal.util.ServerVersion; import org.neo4j.driver.Record; import org.neo4j.driver.Statement; import org.neo4j.driver.Value; import org.neo4j.driver.exceptions.ServiceUnavailableException; import org.neo4j.driver.exceptions.SessionExpiredException; +import org.neo4j.driver.internal.BoltServerAddress; +import org.neo4j.driver.internal.InternalRecord; +import org.neo4j.driver.internal.spi.Connection; import org.neo4j.driver.summary.ResultSummary; import org.neo4j.driver.summary.StatementType; @@ -52,21 +50,18 @@ import static org.junit.jupiter.api.Assertions.assertThrows; import static org.junit.jupiter.api.Assertions.assertTrue; import static org.mockito.Mockito.mock; -import static org.mockito.Mockito.never; -import static org.mockito.Mockito.verify; import static org.mockito.Mockito.when; -import static org.neo4j.driver.internal.messaging.v1.BoltProtocolV1.METADATA_EXTRACTOR; import static org.neo4j.driver.Values.value; import static org.neo4j.driver.Values.values; import static org.neo4j.driver.util.TestUtil.anyServerVersion; import static org.neo4j.driver.util.TestUtil.await; -class AbstractPullAllResponseHandlerTest +public abstract class PullAllResponseHandlerTestBase { @Test void shouldReturnNoFailureWhenAlreadySucceeded() { - AbstractPullAllResponseHandler handler = newHandler(); + PullAllResponseHandler handler = newHandler(); handler.onSuccess( emptyMap() ); Throwable failure = await( handler.failureAsync() ); @@ -77,7 +72,7 @@ void shouldReturnNoFailureWhenAlreadySucceeded() @Test void shouldReturnNoFailureWhenSucceededAfterFailureRequested() { - AbstractPullAllResponseHandler handler = newHandler(); + PullAllResponseHandler handler = newHandler(); CompletableFuture failureFuture = handler.failureAsync().toCompletableFuture(); assertFalse( failureFuture.isDone() ); @@ -91,7 +86,7 @@ void shouldReturnNoFailureWhenSucceededAfterFailureRequested() @Test void shouldReturnFailureWhenAlreadyFailed() { - AbstractPullAllResponseHandler handler = newHandler(); + PullAllResponseHandler handler = newHandler(); RuntimeException failure = new RuntimeException( "Ops" ); handler.onFailure( failure ); @@ -103,7 +98,7 @@ void shouldReturnFailureWhenAlreadyFailed() @Test void shouldReturnFailureWhenFailedAfterFailureRequested() { - AbstractPullAllResponseHandler handler = newHandler(); + PullAllResponseHandler handler = newHandler(); CompletableFuture failureFuture = handler.failureAsync().toCompletableFuture(); assertFalse( failureFuture.isDone() ); @@ -118,7 +113,7 @@ void shouldReturnFailureWhenFailedAfterFailureRequested() @Test void shouldReturnFailureWhenRequestedMultipleTimes() { - AbstractPullAllResponseHandler handler = newHandler(); + PullAllResponseHandler handler = newHandler(); CompletableFuture failureFuture1 = handler.failureAsync().toCompletableFuture(); CompletableFuture failureFuture2 = handler.failureAsync().toCompletableFuture(); @@ -139,7 +134,7 @@ void shouldReturnFailureWhenRequestedMultipleTimes() @Test void shouldReturnFailureOnlyOnceWhenFailedBeforeFailureRequested() { - AbstractPullAllResponseHandler handler = newHandler(); + PullAllResponseHandler handler = newHandler(); ServiceUnavailableException failure = new ServiceUnavailableException( "Connection terminated" ); handler.onFailure( failure ); @@ -151,7 +146,7 @@ void shouldReturnFailureOnlyOnceWhenFailedBeforeFailureRequested() @Test void shouldReturnFailureOnlyOnceWhenFailedAfterFailureRequested() { - AbstractPullAllResponseHandler handler = newHandler(); + PullAllResponseHandler handler = newHandler(); CompletionStage failureFuture = handler.failureAsync(); @@ -162,11 +157,27 @@ void shouldReturnFailureOnlyOnceWhenFailedAfterFailureRequested() assertNull( await( handler.failureAsync() ) ); } + @Test + void shouldReturnSummaryWhenAlreadyFailedAndFailureConsumed() + { + Statement statement = new Statement( "CREATE ()" ); + PullAllResponseHandler handler = newHandler( statement ); + + ServiceUnavailableException failure = new ServiceUnavailableException( "Neo4j unreachable" ); + handler.onFailure( failure ); + + assertEquals( failure, await( handler.failureAsync() ) ); + + ResultSummary summary = await( handler.summaryAsync() ); + assertNotNull( summary ); + assertEquals( statement, summary.statement() ); + } + @Test void shouldReturnSummaryWhenAlreadySucceeded() { Statement statement = new Statement( "CREATE () RETURN 42" ); - AbstractPullAllResponseHandler handler = newHandler( statement ); + PullAllResponseHandler handler = newHandler( statement ); handler.onSuccess( singletonMap( "type", value( "rw" ) ) ); ResultSummary summary = await( handler.summaryAsync() ); @@ -179,7 +190,7 @@ void shouldReturnSummaryWhenAlreadySucceeded() void shouldReturnSummaryWhenSucceededAfterSummaryRequested() { Statement statement = new Statement( "RETURN 'Hi!" ); - AbstractPullAllResponseHandler handler = newHandler( statement ); + PullAllResponseHandler handler = newHandler( statement ); CompletableFuture summaryFuture = handler.summaryAsync().toCompletableFuture(); assertFalse( summaryFuture.isDone() ); @@ -196,7 +207,7 @@ void shouldReturnSummaryWhenSucceededAfterSummaryRequested() @Test void shouldReturnFailureWhenSummaryRequestedWhenAlreadyFailed() { - AbstractPullAllResponseHandler handler = newHandler(); + PullAllResponseHandler handler = newHandler(); RuntimeException failure = new RuntimeException( "Computer is burning" ); handler.onFailure( failure ); @@ -208,7 +219,7 @@ void shouldReturnFailureWhenSummaryRequestedWhenAlreadyFailed() @Test void shouldReturnFailureWhenFailedAfterSummaryRequested() { - AbstractPullAllResponseHandler handler = newHandler(); + PullAllResponseHandler handler = newHandler(); CompletableFuture summaryFuture = handler.summaryAsync().toCompletableFuture(); assertFalse( summaryFuture.isDone() ); @@ -224,7 +235,7 @@ void shouldReturnFailureWhenFailedAfterSummaryRequested() @Test void shouldFailSummaryWhenRequestedMultipleTimes() { - AbstractPullAllResponseHandler handler = newHandler(); + PullAllResponseHandler handler = newHandler(); CompletableFuture summaryFuture1 = handler.summaryAsync().toCompletableFuture(); CompletableFuture summaryFuture2 = handler.summaryAsync().toCompletableFuture(); @@ -237,10 +248,10 @@ void shouldFailSummaryWhenRequestedMultipleTimes() assertTrue( summaryFuture1.isDone() ); assertTrue( summaryFuture2.isDone() ); - Exception e1 = assertThrows( Exception.class, () -> await( summaryFuture1 ) ); + Exception e1 = assertThrows( Exception.class, () -> await( summaryFuture2 ) ); assertEquals( failure, e1 ); - Exception e2 = assertThrows( Exception.class, () -> await( summaryFuture2 ) ); + Exception e2 = assertThrows( Exception.class, () -> await( summaryFuture1 ) ); assertEquals( failure, e2 ); } @@ -248,7 +259,7 @@ void shouldFailSummaryWhenRequestedMultipleTimes() void shouldPropagateFailureOnlyOnceFromSummary() { Statement statement = new Statement( "CREATE INDEX ON :Person(name)" ); - AbstractPullAllResponseHandler handler = newHandler( statement ); + PullAllResponseHandler handler = newHandler( statement ); IllegalStateException failure = new IllegalStateException( "Some state is illegal :(" ); handler.onFailure( failure ); @@ -261,27 +272,11 @@ void shouldPropagateFailureOnlyOnceFromSummary() assertEquals( statement, summary.statement() ); } - @Test - void shouldReturnSummaryWhenAlreadyFailedAndFailureConsumed() - { - Statement statement = new Statement( "CREATE ()" ); - AbstractPullAllResponseHandler handler = newHandler( statement ); - - ServiceUnavailableException failure = new ServiceUnavailableException( "Neo4j unreachable" ); - handler.onFailure( failure ); - - assertEquals( failure, await( handler.failureAsync() ) ); - - ResultSummary summary = await( handler.summaryAsync() ); - assertNotNull( summary ); - assertEquals( statement, summary.statement() ); - } - @Test void shouldPeekSingleAvailableRecord() { List keys = asList( "key1", "key2" ); - AbstractPullAllResponseHandler handler = newHandler( keys ); + PullAllResponseHandler handler = newHandler( keys ); handler.onRecord( values( "a", "b" ) ); Record record = await( handler.peekAsync() ); @@ -295,7 +290,7 @@ void shouldPeekSingleAvailableRecord() void shouldPeekFirstRecordWhenMultipleAvailable() { List keys = asList( "key1", "key2", "key3" ); - AbstractPullAllResponseHandler handler = newHandler( keys ); + PullAllResponseHandler handler = newHandler( keys ); handler.onRecord( values( "a1", "b1", "c1" ) ); handler.onRecord( values( "a2", "b2", "c2" ) ); @@ -313,7 +308,7 @@ void shouldPeekFirstRecordWhenMultipleAvailable() void shouldPeekRecordThatBecomesAvailableLater() { List keys = asList( "key1", "key2" ); - AbstractPullAllResponseHandler handler = newHandler( keys ); + PullAllResponseHandler handler = newHandler( keys ); CompletableFuture recordFuture = handler.peekAsync().toCompletableFuture(); assertFalse( recordFuture.isDone() ); @@ -331,7 +326,7 @@ void shouldPeekRecordThatBecomesAvailableLater() void shouldPeekAvailableNothingAfterSuccess() { List keys = asList( "key1", "key2", "key3" ); - AbstractPullAllResponseHandler handler = newHandler( keys ); + PullAllResponseHandler handler = newHandler( keys ); handler.onRecord( values( 1, 2, 3 ) ); handler.onSuccess( emptyMap() ); @@ -346,7 +341,7 @@ void shouldPeekAvailableNothingAfterSuccess() @Test void shouldPeekNothingAfterSuccess() { - AbstractPullAllResponseHandler handler = newHandler(); + PullAllResponseHandler handler = newHandler(); handler.onSuccess( emptyMap() ); assertNull( await( handler.peekAsync() ) ); @@ -356,7 +351,7 @@ void shouldPeekNothingAfterSuccess() void shouldPeekWhenRequestedMultipleTimes() { List keys = asList( "key1", "key2" ); - AbstractPullAllResponseHandler handler = newHandler( keys ); + PullAllResponseHandler handler = newHandler( keys ); CompletableFuture recordFuture1 = handler.peekAsync().toCompletableFuture(); CompletableFuture recordFuture2 = handler.peekAsync().toCompletableFuture(); @@ -393,7 +388,7 @@ void shouldPeekWhenRequestedMultipleTimes() @Test void shouldPropagateNotConsumedFailureInPeek() { - AbstractPullAllResponseHandler handler = newHandler(); + PullAllResponseHandler handler = newHandler(); RuntimeException failure = new RuntimeException( "Something is wrong" ); handler.onFailure( failure ); @@ -405,7 +400,7 @@ void shouldPropagateNotConsumedFailureInPeek() @Test void shouldPropagateFailureInPeekWhenItBecomesAvailable() { - AbstractPullAllResponseHandler handler = newHandler(); + PullAllResponseHandler handler = newHandler(); CompletableFuture recordFuture = handler.peekAsync().toCompletableFuture(); assertFalse( recordFuture.isDone() ); @@ -420,7 +415,7 @@ void shouldPropagateFailureInPeekWhenItBecomesAvailable() @Test void shouldPropagateFailureInPeekOnlyOnce() { - AbstractPullAllResponseHandler handler = newHandler(); + PullAllResponseHandler handler = newHandler(); RuntimeException failure = new RuntimeException( "Something is wrong" ); handler.onFailure( failure ); @@ -434,7 +429,7 @@ void shouldPropagateFailureInPeekOnlyOnce() void shouldReturnSingleAvailableRecordInNextAsync() { List keys = asList( "key1", "key2" ); - AbstractPullAllResponseHandler handler = newHandler( keys ); + PullAllResponseHandler handler = newHandler( keys ); handler.onRecord( values( "1", "2" ) ); Record record = await( handler.nextAsync() ); @@ -448,7 +443,7 @@ void shouldReturnSingleAvailableRecordInNextAsync() @Test void shouldReturnNoRecordsWhenNoneAvailableInNextAsync() { - AbstractPullAllResponseHandler handler = newHandler( asList( "key1", "key2" ) ); + PullAllResponseHandler handler = newHandler( asList( "key1", "key2" ) ); handler.onSuccess( emptyMap() ); assertNull( await( handler.nextAsync() ) ); @@ -457,7 +452,7 @@ void shouldReturnNoRecordsWhenNoneAvailableInNextAsync() @Test void shouldReturnNoRecordsWhenSuccessComesAfterNextAsync() { - AbstractPullAllResponseHandler handler = newHandler( asList( "key1", "key2" ) ); + PullAllResponseHandler handler = newHandler( asList( "key1", "key2" ) ); CompletableFuture recordFuture = handler.nextAsync().toCompletableFuture(); assertFalse( recordFuture.isDone() ); @@ -472,7 +467,7 @@ void shouldReturnNoRecordsWhenSuccessComesAfterNextAsync() void shouldPullAllAvailableRecordsWithNextAsync() { List keys = asList( "key1", "key2", "key3" ); - AbstractPullAllResponseHandler handler = newHandler( keys ); + PullAllResponseHandler handler = newHandler( keys ); handler.onRecord( values( 1, 2, 3 ) ); handler.onRecord( values( 11, 22, 33 ) ); @@ -516,7 +511,7 @@ void shouldPullAllAvailableRecordsWithNextAsync() void shouldReturnRecordInNextAsyncWhenItBecomesAvailableLater() { List keys = asList( "key1", "key2" ); - AbstractPullAllResponseHandler handler = newHandler( keys ); + PullAllResponseHandler handler = newHandler( keys ); CompletableFuture recordFuture = handler.nextAsync().toCompletableFuture(); assertFalse( recordFuture.isDone() ); @@ -535,7 +530,7 @@ void shouldReturnRecordInNextAsyncWhenItBecomesAvailableLater() void shouldReturnSameRecordOnceWhenRequestedMultipleTimesInNextAsync() { List keys = asList( "key1", "key2" ); - AbstractPullAllResponseHandler handler = newHandler( keys ); + PullAllResponseHandler handler = newHandler( keys ); CompletableFuture recordFuture1 = handler.nextAsync().toCompletableFuture(); CompletableFuture recordFuture2 = handler.nextAsync().toCompletableFuture(); @@ -562,7 +557,7 @@ void shouldReturnSameRecordOnceWhenRequestedMultipleTimesInNextAsync() @Test void shouldPropagateExistingFailureInNextAsync() { - AbstractPullAllResponseHandler handler = newHandler(); + PullAllResponseHandler handler = newHandler(); RuntimeException error = new RuntimeException( "FAILED to read" ); handler.onFailure( error ); @@ -573,7 +568,7 @@ void shouldPropagateExistingFailureInNextAsync() @Test void shouldPropagateFailureInNextAsyncWhenFailureMessagesArrivesLater() { - AbstractPullAllResponseHandler handler = newHandler(); + PullAllResponseHandler handler = newHandler(); CompletableFuture recordFuture = handler.nextAsync().toCompletableFuture(); assertFalse( recordFuture.isDone() ); @@ -586,193 +581,10 @@ void shouldPropagateFailureInNextAsyncWhenFailureMessagesArrivesLater() assertEquals( error, e ); } - @Test - void shouldDisableAutoReadWhenTooManyRecordsArrive() - { - Connection connection = connectionMock(); - AbstractPullAllResponseHandler handler = newHandler( asList( "key1", "key2" ), connection ); - - for ( int i = 0; i < AbstractPullAllResponseHandler.RECORD_BUFFER_HIGH_WATERMARK + 1; i++ ) - { - handler.onRecord( values( 100, 200 ) ); - } - - verify( connection ).disableAutoRead(); - } - - @Test - void shouldEnableAutoReadWhenRecordsRetrievedFromBuffer() - { - Connection connection = connectionMock(); - List keys = asList( "key1", "key2" ); - AbstractPullAllResponseHandler handler = newHandler( keys, connection ); - - int i; - for ( i = 0; i < AbstractPullAllResponseHandler.RECORD_BUFFER_HIGH_WATERMARK + 1; i++ ) - { - handler.onRecord( values( 100, 200 ) ); - } - - verify( connection, never() ).enableAutoRead(); - verify( connection ).disableAutoRead(); - - while ( i-- > AbstractPullAllResponseHandler.RECORD_BUFFER_LOW_WATERMARK - 1 ) - { - Record record = await( handler.nextAsync() ); - assertNotNull( record ); - assertEquals( keys, record.keys() ); - assertEquals( 100, record.get( "key1" ).asInt() ); - assertEquals( 200, record.get( "key2" ).asInt() ); - } - verify( connection ).enableAutoRead(); - } - - @Test - void shouldNotDisableAutoReadWhenSummaryRequested() - { - Connection connection = connectionMock(); - List keys = asList( "key1", "key2" ); - AbstractPullAllResponseHandler handler = newHandler( keys, connection ); - - CompletableFuture summaryFuture = handler.summaryAsync().toCompletableFuture(); - assertFalse( summaryFuture.isDone() ); - - int recordCount = AbstractPullAllResponseHandler.RECORD_BUFFER_HIGH_WATERMARK + 10; - for ( int i = 0; i < recordCount; i++ ) - { - handler.onRecord( values( "a", "b" ) ); - } - - verify( connection, never() ).disableAutoRead(); - - handler.onSuccess( emptyMap() ); - assertTrue( summaryFuture.isDone() ); - - ResultSummary summary = await( summaryFuture ); - assertNotNull( summary ); - - for ( int i = 0; i < recordCount; i++ ) - { - Record record = await( handler.nextAsync() ); - assertNotNull( record ); - assertEquals( keys, record.keys() ); - assertEquals( "a", record.get( "key1" ).asString() ); - assertEquals( "b", record.get( "key2" ).asString() ); - } - - assertNull( await( handler.nextAsync() ) ); - } - - @Test - void shouldNotDisableAutoReadWhenFailureRequested() - { - Connection connection = connectionMock(); - List keys = asList( "key1", "key2" ); - AbstractPullAllResponseHandler handler = newHandler( keys, connection ); - - CompletableFuture failureFuture = handler.failureAsync().toCompletableFuture(); - assertFalse( failureFuture.isDone() ); - - int recordCount = AbstractPullAllResponseHandler.RECORD_BUFFER_HIGH_WATERMARK + 5; - for ( int i = 0; i < recordCount; i++ ) - { - handler.onRecord( values( 123, 456 ) ); - } - - verify( connection, never() ).disableAutoRead(); - - IllegalStateException error = new IllegalStateException( "Wrong config" ); - handler.onFailure( error ); - - assertTrue( failureFuture.isDone() ); - assertEquals( error, await( failureFuture ) ); - - for ( int i = 0; i < recordCount; i++ ) - { - Record record = await( handler.nextAsync() ); - assertNotNull( record ); - assertEquals( keys, record.keys() ); - assertEquals( 123, record.get( "key1" ).asInt() ); - assertEquals( 456, record.get( "key2" ).asInt() ); - } - - assertNull( await( handler.nextAsync() ) ); - } - - @Test - void shouldEnableAutoReadOnConnectionWhenFailureRequestedButNotAvailable() throws Exception - { - Connection connection = connectionMock(); - AbstractPullAllResponseHandler handler = newHandler( asList( "key1", "key2" ), connection ); - - handler.onRecord( values( 1, 2 ) ); - handler.onRecord( values( 3, 4 ) ); - - verify( connection, never() ).enableAutoRead(); - verify( connection, never() ).disableAutoRead(); - - CompletableFuture failureFuture = handler.failureAsync().toCompletableFuture(); - assertFalse( failureFuture.isDone() ); - - verify( connection ).enableAutoRead(); - verify( connection, never() ).disableAutoRead(); - - assertNotNull( await( handler.nextAsync() ) ); - assertNotNull( await( handler.nextAsync() ) ); - - RuntimeException error = new RuntimeException( "Oh my!" ); - handler.onFailure( error ); - - assertTrue( failureFuture.isDone() ); - assertEquals( error, failureFuture.get() ); - } - - @Test - void shouldEnableAutoReadOnConnectionWhenSummaryRequestedButNotAvailable() throws Exception - { - Connection connection = connectionMock(); - AbstractPullAllResponseHandler handler = newHandler( asList( "key1", "key2", "key3" ), connection ); - - handler.onRecord( values( 1, 2, 3 ) ); - handler.onRecord( values( 4, 5, 6 ) ); - - verify( connection, never() ).enableAutoRead(); - verify( connection, never() ).disableAutoRead(); - - CompletableFuture summaryFuture = handler.summaryAsync().toCompletableFuture(); - assertFalse( summaryFuture.isDone() ); - - verify( connection ).enableAutoRead(); - verify( connection, never() ).disableAutoRead(); - - assertNotNull( await( handler.nextAsync() ) ); - assertNotNull( await( handler.nextAsync() ) ); - - handler.onSuccess( emptyMap() ); - - assertTrue( summaryFuture.isDone() ); - assertNotNull( summaryFuture.get() ); - } - - @Test - void shouldNotDisableAutoReadWhenAutoReadManagementDisabled() - { - Connection connection = connectionMock(); - AbstractPullAllResponseHandler handler = newHandler( asList( "key1", "key2" ), connection ); - handler.disableAutoReadManagement(); - - for ( int i = 0; i < AbstractPullAllResponseHandler.RECORD_BUFFER_HIGH_WATERMARK + 1; i++ ) - { - handler.onRecord( values( 100, 200 ) ); - } - - verify( connection, never() ).disableAutoRead(); - } - @Test void shouldPropagateFailureFromListAsync() { - AbstractPullAllResponseHandler handler = newHandler(); + PullAllResponseHandler handler = newHandler(); RuntimeException error = new RuntimeException( "Hi!" ); handler.onFailure( error ); @@ -783,7 +595,7 @@ void shouldPropagateFailureFromListAsync() @Test void shouldPropagateFailureAfterRecordFromListAsync() { - AbstractPullAllResponseHandler handler = newHandler( asList( "key1", "key2" ) ); + PullAllResponseHandler handler = newHandler( asList( "key1", "key2" ) ); handler.onRecord( values( "a", "b" ) ); @@ -797,7 +609,7 @@ void shouldPropagateFailureAfterRecordFromListAsync() @Test void shouldFailListAsyncWhenTransformationFunctionThrows() { - AbstractPullAllResponseHandler handler = newHandler( asList( "key1", "key2" ) ); + PullAllResponseHandler handler = newHandler( asList( "key1", "key2" ) ); handler.onRecord( values( 1, 2 ) ); handler.onRecord( values( 3, 4 ) ); handler.onSuccess( emptyMap() ); @@ -820,30 +632,17 @@ void shouldFailListAsyncWhenTransformationFunctionThrows() @Test void shouldReturnEmptyListInListAsyncAfterSuccess() { - AbstractPullAllResponseHandler handler = newHandler(); + PullAllResponseHandler handler = newHandler(); handler.onSuccess( emptyMap() ); assertEquals( emptyList(), await( handler.listAsync( Function.identity() ) ) ); } - @Test - void shouldReturnEmptyListInListAsyncAfterFailure() - { - AbstractPullAllResponseHandler handler = newHandler(); - - RuntimeException error = new RuntimeException( "Hi" ); - handler.onFailure( error ); - - // consume the error - assertEquals( error, await( handler.failureAsync() ) ); - assertEquals( emptyList(), await( handler.listAsync( Function.identity() ) ) ); - } - @Test void shouldReturnTransformedListInListAsync() { - AbstractPullAllResponseHandler handler = newHandler( singletonList( "key1" ) ); + PullAllResponseHandler handler = newHandler( singletonList( "key1" ) ); handler.onRecord( values( 1 ) ); handler.onRecord( values( 2 ) ); @@ -860,7 +659,7 @@ void shouldReturnTransformedListInListAsync() void shouldReturnNotTransformedListInListAsync() { List keys = asList( "key1", "key2" ); - AbstractPullAllResponseHandler handler = newHandler( keys ); + PullAllResponseHandler handler = newHandler( keys ); Value[] fields1 = values( "a", "b" ); Value[] fields2 = values( "c", "d" ); @@ -881,245 +680,38 @@ void shouldReturnNotTransformedListInListAsync() assertEquals( expectedRecords, list ); } - @Test - void shouldConsumeAfterSuccessWithRecords() - { - AbstractPullAllResponseHandler handler = newHandler( singletonList( "key1" ) ); - handler.onRecord( values( 1 ) ); - handler.onRecord( values( 2 ) ); - handler.onSuccess( emptyMap() ); - - assertNotNull( await( handler.consumeAsync() ) ); - - assertNoRecordsCanBeFetched( handler ); - } - - @Test - void shouldConsumeAfterSuccessWithoutRecords() - { - AbstractPullAllResponseHandler handler = newHandler(); - handler.onSuccess( emptyMap() ); - - assertNotNull( await( handler.consumeAsync() ) ); - - assertNoRecordsCanBeFetched( handler ); - } - - @Test - void shouldConsumeAfterFailureWithRecords() - { - AbstractPullAllResponseHandler handler = newHandler( singletonList( "key1" ) ); - handler.onRecord( values( 1 ) ); - handler.onRecord( values( 2 ) ); - RuntimeException error = new RuntimeException( "Hi" ); - handler.onFailure( error ); - - RuntimeException e = assertThrows( RuntimeException.class, () -> await( handler.consumeAsync() ) ); - assertEquals( error, e ); - assertNoRecordsCanBeFetched( handler ); - } - - @Test - void shouldConsumeAfterFailureWithoutRecords() - { - AbstractPullAllResponseHandler handler = newHandler(); - RuntimeException error = new RuntimeException( "Hi" ); - handler.onFailure( error ); - - RuntimeException e = assertThrows( RuntimeException.class, () -> await( handler.consumeAsync() ) ); - assertEquals( error, e ); - assertNoRecordsCanBeFetched( handler ); - } - - @Test - void shouldConsumeAfterProcessedFailureWithRecords() - { - AbstractPullAllResponseHandler handler = newHandler( singletonList( "key1" ) ); - handler.onRecord( values( 1 ) ); - handler.onRecord( values( 2 ) ); - RuntimeException error = new RuntimeException( "Hi" ); - handler.onFailure( error ); - - // process failure - assertEquals( error, await( handler.failureAsync() ) ); - // consume all buffered records - assertNotNull( await( handler.consumeAsync() ) ); - - assertNoRecordsCanBeFetched( handler ); - } - - @Test - void shouldConsumeAfterProcessedFailureWithoutRecords() - { - AbstractPullAllResponseHandler handler = newHandler(); - RuntimeException error = new RuntimeException( "Hi" ); - handler.onFailure( error ); - - // process failure - assertEquals( error, await( handler.failureAsync() ) ); - // consume all buffered records - assertNotNull( await( handler.consumeAsync() ) ); - - assertNoRecordsCanBeFetched( handler ); - } - - @Test - void shouldConsumeUntilSuccess() - { - AbstractPullAllResponseHandler handler = newHandler( asList( "key1", "key2" ) ); - handler.onRecord( values( 1, 2 ) ); - handler.onRecord( values( 3, 4 ) ); - - CompletableFuture consumeFuture = handler.consumeAsync().toCompletableFuture(); - assertFalse( consumeFuture.isDone() ); - - handler.onRecord( values( 5, 6 ) ); - handler.onRecord( values( 7, 8 ) ); - assertFalse( consumeFuture.isDone() ); - - handler.onSuccess( emptyMap() ); - - assertTrue( consumeFuture.isDone() ); - assertNotNull( await( consumeFuture ) ); - - assertNoRecordsCanBeFetched( handler ); - } - - @Test - void shouldConsumeUntilFailure() - { - AbstractPullAllResponseHandler handler = newHandler( asList( "key1", "key2" ) ); - handler.onRecord( values( 1, 2 ) ); - handler.onRecord( values( 3, 4 ) ); - - CompletableFuture consumeFuture = handler.consumeAsync().toCompletableFuture(); - assertFalse( consumeFuture.isDone() ); - - handler.onRecord( values( 5, 6 ) ); - handler.onRecord( values( 7, 8 ) ); - assertFalse( consumeFuture.isDone() ); - - RuntimeException error = new RuntimeException( "Hi" ); - handler.onFailure( error ); - - assertTrue( consumeFuture.isDone() ); - assertTrue( consumeFuture.isCompletedExceptionally() ); - RuntimeException e = assertThrows( RuntimeException.class, () -> await( consumeFuture ) ); - assertEquals( error, e ); - assertNoRecordsCanBeFetched( handler ); - } - - @Test - void shouldReturnNoRecordsWhenConsumed() - { - AbstractPullAllResponseHandler handler = newHandler( asList( "key1", "key2" ) ); - handler.onRecord( values( 1, 2 ) ); - handler.onRecord( values( 3, 4 ) ); - - CompletableFuture consumeFuture = handler.consumeAsync().toCompletableFuture(); - assertFalse( consumeFuture.isDone() ); - - CompletionStage peekStage1 = handler.peekAsync(); - CompletionStage nextStage1 = handler.nextAsync(); - - handler.onRecord( values( 5, 6 ) ); - handler.onRecord( values( 7, 8 ) ); - - CompletionStage peekStage2 = handler.peekAsync(); - CompletionStage nextStage2 = handler.nextAsync(); - assertFalse( consumeFuture.isDone() ); - - handler.onSuccess( emptyMap() ); - - assertNull( await( peekStage1 ) ); - assertNull( await( nextStage1 ) ); - assertNull( await( peekStage2 ) ); - assertNull( await( nextStage2 ) ); - - assertTrue( consumeFuture.isDone() ); - assertNotNull( await( consumeFuture ) ); - } - - @Test - void shouldReceiveSummaryAfterConsume() - { - Statement statement = new Statement( "RETURN 'Hello!'" ); - AbstractPullAllResponseHandler handler = newHandler( statement, singletonList( "key" ) ); - handler.onRecord( values( "Hi!" ) ); - handler.onSuccess( singletonMap( "type", value( "rw" ) ) ); - - ResultSummary summary1 = await( handler.consumeAsync() ); - assertEquals( statement.text(), summary1.statement().text() ); - assertEquals( StatementType.READ_WRITE, summary1.statementType() ); - - ResultSummary summary2 = await( handler.summaryAsync() ); - assertEquals( statement.text(), summary2.statement().text() ); - assertEquals( StatementType.READ_WRITE, summary2.statementType() ); - } - - private static AbstractPullAllResponseHandler newHandler() + protected T newHandler() { return newHandler( new Statement( "RETURN 1" ) ); } - private static AbstractPullAllResponseHandler newHandler( Statement statement ) + protected T newHandler( Statement statement ) { return newHandler( statement, emptyList() ); } - private static AbstractPullAllResponseHandler newHandler( List statementKeys ) + protected T newHandler( List statementKeys ) { return newHandler( new Statement( "RETURN 1" ), statementKeys, connectionMock() ); } - private static AbstractPullAllResponseHandler newHandler( Statement statement, List statementKeys ) + protected T newHandler( Statement statement, List statementKeys ) { return newHandler( statement, statementKeys, connectionMock() ); } - private static AbstractPullAllResponseHandler newHandler( List statementKeys, Connection connection ) + protected T newHandler( List statementKeys, Connection connection ) { return newHandler( new Statement( "RETURN 1" ), statementKeys, connection ); } - private static AbstractPullAllResponseHandler newHandler( Statement statement, List statementKeys, - Connection connection ) - { - RunResponseHandler runResponseHandler = new RunResponseHandler( new CompletableFuture<>(), METADATA_EXTRACTOR ); - runResponseHandler.onSuccess( singletonMap( "fields", value( statementKeys ) ) ); - return new TestPullAllResponseHandler( statement, runResponseHandler, connection ); - } + protected abstract T newHandler( Statement statement, List statementKeys, Connection connection ); - private static Connection connectionMock() + protected Connection connectionMock() { Connection connection = mock( Connection.class ); when( connection.serverAddress() ).thenReturn( BoltServerAddress.LOCAL_DEFAULT ); when( connection.serverVersion() ).thenReturn( anyServerVersion() ); return connection; } - - private static void assertNoRecordsCanBeFetched( AbstractPullAllResponseHandler handler ) - { - assertNull( await( handler.peekAsync() ) ); - assertNull( await( handler.nextAsync() ) ); - assertEquals( emptyList(), await( handler.listAsync( Function.identity() ) ) ); - } - - private static class TestPullAllResponseHandler extends AbstractPullAllResponseHandler - { - public TestPullAllResponseHandler( Statement statement, RunResponseHandler runResponseHandler, Connection connection ) - { - super( statement, runResponseHandler, connection, METADATA_EXTRACTOR ); - } - - @Override - protected void afterSuccess( Map metadata ) - { - } - - @Override - protected void afterFailure( Throwable error ) - { - } - } } diff --git a/driver/src/test/java/org/neo4j/driver/internal/handlers/SessionPullAllResponseHandlerTest.java b/driver/src/test/java/org/neo4j/driver/internal/handlers/SessionPullResponseCompletionListenerTest.java similarity index 67% rename from driver/src/test/java/org/neo4j/driver/internal/handlers/SessionPullAllResponseHandlerTest.java rename to driver/src/test/java/org/neo4j/driver/internal/handlers/SessionPullResponseCompletionListenerTest.java index 06a9bb54e8..eda0779029 100644 --- a/driver/src/test/java/org/neo4j/driver/internal/handlers/SessionPullAllResponseHandlerTest.java +++ b/driver/src/test/java/org/neo4j/driver/internal/handlers/SessionPullResponseCompletionListenerTest.java @@ -26,7 +26,9 @@ import org.neo4j.driver.internal.BoltServerAddress; import org.neo4j.driver.internal.BookmarkHolder; import org.neo4j.driver.internal.InternalBookmark; +import org.neo4j.driver.internal.handlers.pulln.BasicPullResponseHandler; import org.neo4j.driver.internal.spi.Connection; +import org.neo4j.driver.internal.spi.ResponseHandler; import static java.util.Collections.emptyMap; import static java.util.Collections.singletonMap; @@ -37,13 +39,14 @@ import static org.neo4j.driver.internal.messaging.v1.BoltProtocolV1.METADATA_EXTRACTOR; import static org.neo4j.driver.util.TestUtil.anyServerVersion; -class SessionPullAllResponseHandlerTest +class SessionPullResponseCompletionListenerTest { @Test void shouldReleaseConnectionOnSuccess() { Connection connection = newConnectionMock(); - SessionPullAllResponseHandler handler = newHandler( connection ); + PullResponseCompletionListener listener = new SessionPullResponseCompletionListener( connection, BookmarkHolder.NO_OP ); + ResponseHandler handler = newHandler( connection, listener ); handler.onSuccess( emptyMap() ); @@ -54,7 +57,8 @@ void shouldReleaseConnectionOnSuccess() void shouldReleaseConnectionOnFailure() { Connection connection = newConnectionMock(); - SessionPullAllResponseHandler handler = newHandler( connection ); + PullResponseCompletionListener listener = new SessionPullResponseCompletionListener( connection, BookmarkHolder.NO_OP ); + ResponseHandler handler = newHandler( connection, listener ); handler.onFailure( new RuntimeException() ); @@ -64,24 +68,25 @@ void shouldReleaseConnectionOnFailure() @Test void shouldUpdateBookmarksOnSuccess() { + Connection connection = newConnectionMock(); String bookmarkValue = "neo4j:bookmark:v1:tx42"; BookmarkHolder bookmarkHolder = mock( BookmarkHolder.class ); - SessionPullAllResponseHandler handler = newHandler( newConnectionMock(), bookmarkHolder ); + PullResponseCompletionListener listener = new SessionPullResponseCompletionListener( connection, bookmarkHolder ); + ResponseHandler handler = newHandler( connection, listener ); handler.onSuccess( singletonMap( "bookmark", value( bookmarkValue ) ) ); verify( bookmarkHolder ).setBookmark( InternalBookmark.parse( bookmarkValue ) ); } - private static SessionPullAllResponseHandler newHandler( Connection connection ) - { - return newHandler( connection, BookmarkHolder.NO_OP ); - } - - private static SessionPullAllResponseHandler newHandler( Connection connection, BookmarkHolder bookmarkHolder ) + private static ResponseHandler newHandler( Connection connection, PullResponseCompletionListener listener ) { RunResponseHandler runHandler = new RunResponseHandler( new CompletableFuture<>(), METADATA_EXTRACTOR ); - return new SessionPullAllResponseHandler( new Statement( "RETURN 1" ), runHandler, connection, bookmarkHolder, METADATA_EXTRACTOR ); + BasicPullResponseHandler handler = + new BasicPullResponseHandler( new Statement( "RETURN 1" ), runHandler, connection, METADATA_EXTRACTOR, listener ); + handler.installRecordConsumer( ( record, throwable ) -> {} ); + handler.installSummaryConsumer( ( resultSummary, throwable ) -> {} ); + return handler; } private static Connection newConnectionMock() diff --git a/driver/src/test/java/org/neo4j/driver/internal/handlers/TransactionPullAllResponseHandlerTest.java b/driver/src/test/java/org/neo4j/driver/internal/handlers/TransactionPullResponseCompletionListenerTest.java similarity index 82% rename from driver/src/test/java/org/neo4j/driver/internal/handlers/TransactionPullAllResponseHandlerTest.java rename to driver/src/test/java/org/neo4j/driver/internal/handlers/TransactionPullResponseCompletionListenerTest.java index 3e9ad7283c..16ab6db684 100644 --- a/driver/src/test/java/org/neo4j/driver/internal/handlers/TransactionPullAllResponseHandlerTest.java +++ b/driver/src/test/java/org/neo4j/driver/internal/handlers/TransactionPullResponseCompletionListenerTest.java @@ -30,6 +30,8 @@ import org.neo4j.driver.exceptions.TransientException; import org.neo4j.driver.internal.BoltServerAddress; import org.neo4j.driver.internal.async.ExplicitTransaction; +import org.neo4j.driver.internal.handlers.pulln.BasicPullResponseHandler; +import org.neo4j.driver.internal.handlers.pulln.PullResponseHandler; import org.neo4j.driver.internal.spi.Connection; import static org.mockito.Mockito.mock; @@ -38,7 +40,7 @@ import static org.neo4j.driver.internal.messaging.v1.BoltProtocolV1.METADATA_EXTRACTOR; import static org.neo4j.driver.util.TestUtil.anyServerVersion; -class TransactionPullAllResponseHandlerTest +class TransactionPullResponseCompletionListenerTest { @Test void shouldMarkTransactionAsTerminatedOnFailures() @@ -62,9 +64,12 @@ private static void testErrorHandling( Throwable error ) when( connection.serverAddress() ).thenReturn( BoltServerAddress.LOCAL_DEFAULT ); when( connection.serverVersion() ).thenReturn( anyServerVersion() ); ExplicitTransaction tx = mock( ExplicitTransaction.class ); + TransactionPullResponseCompletionListener listener = new TransactionPullResponseCompletionListener( tx ); RunResponseHandler runHandler = new RunResponseHandler( new CompletableFuture<>(), METADATA_EXTRACTOR ); - PullAllResponseHandler handler = new TransactionPullAllResponseHandler( new Statement( "RETURN 1" ), runHandler, - connection, tx, METADATA_EXTRACTOR ); + PullResponseHandler handler = new BasicPullResponseHandler( new Statement( "RETURN 1" ), runHandler, + connection, METADATA_EXTRACTOR, listener ); + handler.installRecordConsumer( ( record, throwable ) -> {} ); + handler.installSummaryConsumer( ( resultSummary, throwable ) -> {} ); handler.onFailure( error ); diff --git a/driver/src/test/java/org/neo4j/driver/internal/handlers/pulln/AutoPullResponseHandlerTest.java b/driver/src/test/java/org/neo4j/driver/internal/handlers/pulln/AutoPullResponseHandlerTest.java new file mode 100644 index 0000000000..ae3e3c0c07 --- /dev/null +++ b/driver/src/test/java/org/neo4j/driver/internal/handlers/pulln/AutoPullResponseHandlerTest.java @@ -0,0 +1,49 @@ +/* + * 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.handlers.pulln; + +import java.util.List; +import java.util.concurrent.CompletableFuture; + +import org.neo4j.driver.Statement; +import org.neo4j.driver.internal.handlers.PullAllResponseHandlerTestBase; +import org.neo4j.driver.internal.handlers.PullResponseCompletionListener; +import org.neo4j.driver.internal.handlers.RunResponseHandler; +import org.neo4j.driver.internal.spi.Connection; + +import static java.util.Collections.singletonMap; +import static org.mockito.Mockito.mock; +import static org.neo4j.driver.Values.value; +import static org.neo4j.driver.internal.handlers.pulln.FetchSizeUtil.DEFAULT_FETCH_SIZE; +import static org.neo4j.driver.internal.messaging.v1.BoltProtocolV1.METADATA_EXTRACTOR; + +class AutoPullResponseHandlerTest extends PullAllResponseHandlerTestBase +{ + @Override + protected AutoPullResponseHandler newHandler( Statement statement, List statementKeys, Connection connection ) + { + RunResponseHandler runResponseHandler = new RunResponseHandler( new CompletableFuture<>(), METADATA_EXTRACTOR ); + runResponseHandler.onSuccess( singletonMap( "fields", value( statementKeys ) ) ); + AutoPullResponseHandler handler = + new AutoPullResponseHandler( statement, runResponseHandler, connection, METADATA_EXTRACTOR, mock( PullResponseCompletionListener.class ), + DEFAULT_FETCH_SIZE ); + handler.prePopulateRecords(); + return handler; + } +} diff --git a/driver/src/test/java/org/neo4j/driver/internal/handlers/pulln/AbstractBasicPullResponseHandlerTestBase.java b/driver/src/test/java/org/neo4j/driver/internal/handlers/pulln/BasicPullResponseHandlerTestBase.java similarity index 78% rename from driver/src/test/java/org/neo4j/driver/internal/handlers/pulln/AbstractBasicPullResponseHandlerTestBase.java rename to driver/src/test/java/org/neo4j/driver/internal/handlers/pulln/BasicPullResponseHandlerTestBase.java index f14d80f557..01e500b8dc 100644 --- a/driver/src/test/java/org/neo4j/driver/internal/handlers/pulln/AbstractBasicPullResponseHandlerTestBase.java +++ b/driver/src/test/java/org/neo4j/driver/internal/handlers/pulln/BasicPullResponseHandlerTestBase.java @@ -27,7 +27,7 @@ import java.util.stream.Stream; import org.neo4j.driver.internal.BoltServerAddress; -import org.neo4j.driver.internal.handlers.pulln.BasicPullResponseHandler.Status; +import org.neo4j.driver.internal.handlers.pulln.PullResponseHandler.Status; import org.neo4j.driver.internal.messaging.request.DiscardMessage; import org.neo4j.driver.internal.messaging.request.PullMessage; import org.neo4j.driver.internal.spi.Connection; @@ -45,17 +45,17 @@ import static org.mockito.Mockito.verify; import static org.mockito.Mockito.verifyNoMoreInteractions; import static org.mockito.Mockito.when; -import static org.neo4j.driver.internal.handlers.pulln.BasicPullResponseHandler.Status.CANCELED; -import static org.neo4j.driver.internal.handlers.pulln.BasicPullResponseHandler.Status.DONE; -import static org.neo4j.driver.internal.handlers.pulln.BasicPullResponseHandler.Status.FAILED; -import static org.neo4j.driver.internal.handlers.pulln.BasicPullResponseHandler.Status.READY; -import static org.neo4j.driver.internal.handlers.pulln.BasicPullResponseHandler.Status.STREAMING; +import static org.neo4j.driver.internal.handlers.pulln.PullResponseHandler.Status.CANCELED; +import static org.neo4j.driver.internal.handlers.pulln.PullResponseHandler.Status.SUCCEEDED; +import static org.neo4j.driver.internal.handlers.pulln.PullResponseHandler.Status.FAILED; +import static org.neo4j.driver.internal.handlers.pulln.PullResponseHandler.Status.READY; +import static org.neo4j.driver.internal.handlers.pulln.PullResponseHandler.Status.STREAMING; -abstract class AbstractBasicPullResponseHandlerTestBase +abstract class BasicPullResponseHandlerTestBase { protected abstract void shouldHandleSuccessWithSummary( Status status ); protected abstract void shouldHandleFailure( Status status ); - protected abstract AbstractBasicPullResponseHandler newResponseHandlerWithStatus( Connection conn, BiConsumer recordConsumer, + protected abstract BasicPullResponseHandler newResponseHandlerWithStatus( Connection conn, BiConsumer recordConsumer, BiConsumer summaryConsumer, Status status ); // on success with summary @@ -72,7 +72,7 @@ void shouldRequestMoreWithHasMore() throws Throwable { // Given a handler in streaming state Connection conn = mockConnection(); - AbstractBasicPullResponseHandler handler = newResponseHandlerWithStatus( conn, STREAMING ); + BasicPullResponseHandler handler = newResponseHandlerWithStatus( conn, STREAMING ); // When handler.request( 100 ); // I append a request to ask for more @@ -91,7 +91,7 @@ void shouldInformSummaryConsumerSuccessWithHasMore() throws Throwable Connection conn = mockConnection(); BiConsumer recordConsumer = mock( BiConsumer.class ); BiConsumer summaryConsumer = mock( BiConsumer.class ); - AbstractBasicPullResponseHandler handler = newResponseHandlerWithStatus( conn, recordConsumer, summaryConsumer, STREAMING ); + BasicPullResponseHandler handler = newResponseHandlerWithStatus( conn, recordConsumer, summaryConsumer, STREAMING ); // When handler.onSuccess( metaWithHasMoreEqualsTrue() ); @@ -108,7 +108,7 @@ void shouldDiscardIfStreamingIsCanceled() throws Throwable { // Given a handler in streaming state Connection conn = mockConnection(); - AbstractBasicPullResponseHandler handler = newResponseHandlerWithStatus( conn, CANCELED ); + BasicPullResponseHandler handler = newResponseHandlerWithStatus( conn, CANCELED ); handler.onSuccess( metaWithHasMoreEqualsTrue() ); // Then @@ -132,7 +132,7 @@ void shouldReportRecordInStreaming() throws Throwable Connection conn = mockConnection(); BiConsumer recordConsumer = mock( BiConsumer.class ); BiConsumer summaryConsumer = mock( BiConsumer.class ); - AbstractBasicPullResponseHandler handler = newResponseHandlerWithStatus( conn, recordConsumer, summaryConsumer, STREAMING ); + BasicPullResponseHandler handler = newResponseHandlerWithStatus( conn, recordConsumer, summaryConsumer, STREAMING ); // When handler.onRecord( new Value[0] ); @@ -152,7 +152,7 @@ void shouldNotReportRecordWhenNotStreaming( Status status ) throws Throwable Connection conn = mockConnection(); BiConsumer recordConsumer = mock( BiConsumer.class ); BiConsumer summaryConsumer = mock( BiConsumer.class ); - AbstractBasicPullResponseHandler handler = newResponseHandlerWithStatus( conn, recordConsumer, summaryConsumer, status ); + BasicPullResponseHandler handler = newResponseHandlerWithStatus( conn, recordConsumer, summaryConsumer, status ); // When handler.onRecord( new Value[0] ); @@ -169,7 +169,7 @@ void shouldStayInStreaming() throws Throwable { // Given Connection conn = mockConnection(); - AbstractBasicPullResponseHandler handler = newResponseHandlerWithStatus( conn, STREAMING ); + BasicPullResponseHandler handler = newResponseHandlerWithStatus( conn, STREAMING ); // When handler.request( 100 ); @@ -183,7 +183,7 @@ void shouldPullAndSwitchStreamingInReady() throws Throwable { // Given Connection conn = mockConnection(); - AbstractBasicPullResponseHandler handler = newResponseHandlerWithStatus( conn, READY ); + BasicPullResponseHandler handler = newResponseHandlerWithStatus( conn, READY ); // When handler.request( 100 ); @@ -199,7 +199,7 @@ void shouldStayInCancel() throws Throwable { // Given Connection conn = mockConnection(); - AbstractBasicPullResponseHandler handler = newResponseHandlerWithStatus( conn, CANCELED ); + BasicPullResponseHandler handler = newResponseHandlerWithStatus( conn, CANCELED ); // When handler.cancel(); @@ -214,7 +214,7 @@ void shouldSwitchFromStreamingToCancel() throws Throwable { // Given Connection conn = mockConnection(); - AbstractBasicPullResponseHandler handler = newResponseHandlerWithStatus( conn, STREAMING ); + BasicPullResponseHandler handler = newResponseHandlerWithStatus( conn, STREAMING ); // When handler.cancel(); @@ -229,7 +229,7 @@ void shouldSwitchFromReadyToCancel() throws Throwable { // Given Connection conn = mockConnection(); - AbstractBasicPullResponseHandler handler = newResponseHandlerWithStatus( conn, READY ); + BasicPullResponseHandler handler = newResponseHandlerWithStatus( conn, READY ); // When handler.cancel(); @@ -247,7 +247,7 @@ static Connection mockConnection() return conn; } - private AbstractBasicPullResponseHandler newResponseHandlerWithStatus( Connection conn, Status status ) + private BasicPullResponseHandler newResponseHandlerWithStatus( Connection conn, Status status ) { BiConsumer recordConsumer = mock( BiConsumer.class ); BiConsumer summaryConsumer = mock( BiConsumer.class ); @@ -263,11 +263,11 @@ private static HashMap metaWithHasMoreEqualsTrue() private static Stream allStatusExceptStreaming() { - return Stream.of( DONE, FAILED, CANCELED, READY ); + return Stream.of( SUCCEEDED, FAILED, CANCELED, READY ); } private static Stream allStatus() { - return Stream.of( DONE, FAILED, CANCELED, STREAMING, READY ); + return Stream.of( SUCCEEDED, FAILED, CANCELED, STREAMING, READY ); } } diff --git a/driver/src/test/java/org/neo4j/driver/internal/handlers/pulln/SessionPullResponseHandlerTest.java b/driver/src/test/java/org/neo4j/driver/internal/handlers/pulln/SessionPullResponseCompletionListenerTest.java similarity index 72% rename from driver/src/test/java/org/neo4j/driver/internal/handlers/pulln/SessionPullResponseHandlerTest.java rename to driver/src/test/java/org/neo4j/driver/internal/handlers/pulln/SessionPullResponseCompletionListenerTest.java index f4b2f0d4b6..c748cfe6cd 100644 --- a/driver/src/test/java/org/neo4j/driver/internal/handlers/pulln/SessionPullResponseHandlerTest.java +++ b/driver/src/test/java/org/neo4j/driver/internal/handlers/pulln/SessionPullResponseCompletionListenerTest.java @@ -21,13 +21,14 @@ import java.util.Collections; import java.util.function.BiConsumer; +import org.neo4j.driver.Record; +import org.neo4j.driver.Statement; import org.neo4j.driver.internal.BookmarkHolder; import org.neo4j.driver.internal.handlers.RunResponseHandler; -import org.neo4j.driver.internal.handlers.pulln.BasicPullResponseHandler.Status; +import org.neo4j.driver.internal.handlers.SessionPullResponseCompletionListener; +import org.neo4j.driver.internal.handlers.pulln.PullResponseHandler.Status; import org.neo4j.driver.internal.messaging.v4.BoltProtocolV4; import org.neo4j.driver.internal.spi.Connection; -import org.neo4j.driver.Record; -import org.neo4j.driver.Statement; import org.neo4j.driver.summary.ResultSummary; import static org.hamcrest.CoreMatchers.equalTo; @@ -36,10 +37,9 @@ import static org.mockito.ArgumentMatchers.eq; import static org.mockito.Mockito.mock; import static org.mockito.Mockito.verify; -import static org.neo4j.driver.internal.handlers.pulln.BasicPullResponseHandler.Status.DONE; -import static org.neo4j.driver.internal.handlers.pulln.BasicPullResponseHandler.Status.FAILED; +import static org.neo4j.driver.internal.handlers.pulln.PullResponseHandler.Status.FAILED; -class SessionPullResponseHandlerTest extends AbstractBasicPullResponseHandlerTestBase +class SessionPullResponseCompletionListenerTest extends BasicPullResponseHandlerTestBase { @Override protected void shouldHandleSuccessWithSummary( Status status ) @@ -49,13 +49,13 @@ protected void shouldHandleSuccessWithSummary( Status status ) BiConsumer recordConsumer = mock( BiConsumer.class ); BiConsumer summaryConsumer = mock( BiConsumer.class ); BookmarkHolder bookmarkHolder = mock( BookmarkHolder.class ); - SessionPullResponseHandler handler = newSessionResponseHandler( conn, recordConsumer, summaryConsumer, bookmarkHolder, status); + PullResponseHandler handler = newSessionResponseHandler( conn, recordConsumer, summaryConsumer, bookmarkHolder, status); // When handler.onSuccess( Collections.emptyMap() ); // Then - assertThat( handler.status(), equalTo( DONE ) ); +// assertThat( handler.status(), equalTo( SUCCEEDED ) ); verify( conn ).release(); verify( bookmarkHolder ).setBookmark( any() ); verify( recordConsumer ).accept( null, null ); @@ -69,7 +69,7 @@ protected void shouldHandleFailure( Status status ) Connection conn = mockConnection(); BiConsumer recordConsumer = mock( BiConsumer.class ); BiConsumer summaryConsumer = mock( BiConsumer.class ); - AbstractBasicPullResponseHandler handler = newResponseHandlerWithStatus( conn, recordConsumer, summaryConsumer, status ); + BasicPullResponseHandler handler = newResponseHandlerWithStatus( conn, recordConsumer, summaryConsumer, status ); // When RuntimeException error = new RuntimeException( "I am an error" ); @@ -79,23 +79,24 @@ protected void shouldHandleFailure( Status status ) assertThat( handler.status(), equalTo( FAILED ) ); verify( conn ).release(); verify( recordConsumer ).accept( null, error ); - verify( summaryConsumer ).accept( any( ResultSummary.class ), eq( null ) ); + verify( summaryConsumer ).accept( any( ResultSummary.class ), eq( error ) ); } @Override - protected AbstractBasicPullResponseHandler newResponseHandlerWithStatus( Connection conn, BiConsumer recordConsumer, + protected BasicPullResponseHandler newResponseHandlerWithStatus( Connection conn, BiConsumer recordConsumer, BiConsumer summaryConsumer, Status status ) { - BookmarkHolder bookmarkHolder = mock( BookmarkHolder.class ); + BookmarkHolder bookmarkHolder = BookmarkHolder.NO_OP; return newSessionResponseHandler( conn, recordConsumer, summaryConsumer, bookmarkHolder, status ); } - private static SessionPullResponseHandler newSessionResponseHandler( Connection conn, BiConsumer recordConsumer, + private static BasicPullResponseHandler newSessionResponseHandler( Connection conn, BiConsumer recordConsumer, BiConsumer summaryConsumer, BookmarkHolder bookmarkHolder, Status status ) { RunResponseHandler runHandler = mock( RunResponseHandler.class ); - SessionPullResponseHandler handler = - new SessionPullResponseHandler( mock( Statement.class ), runHandler, conn, bookmarkHolder, BoltProtocolV4.METADATA_EXTRACTOR ); + SessionPullResponseCompletionListener listener = new SessionPullResponseCompletionListener( conn, bookmarkHolder ); + BasicPullResponseHandler handler = + new BasicPullResponseHandler( mock( Statement.class ), runHandler, conn, BoltProtocolV4.METADATA_EXTRACTOR, listener ); handler.installRecordConsumer( recordConsumer ); handler.installSummaryConsumer( summaryConsumer ); diff --git a/driver/src/test/java/org/neo4j/driver/internal/handlers/pulln/TransactionPullResponseHandlerTest.java b/driver/src/test/java/org/neo4j/driver/internal/handlers/pulln/TransactionPullResponseCompletionListenerTest.java similarity index 67% rename from driver/src/test/java/org/neo4j/driver/internal/handlers/pulln/TransactionPullResponseHandlerTest.java rename to driver/src/test/java/org/neo4j/driver/internal/handlers/pulln/TransactionPullResponseCompletionListenerTest.java index a0b5c0e324..b70218f232 100644 --- a/driver/src/test/java/org/neo4j/driver/internal/handlers/pulln/TransactionPullResponseHandlerTest.java +++ b/driver/src/test/java/org/neo4j/driver/internal/handlers/pulln/TransactionPullResponseCompletionListenerTest.java @@ -25,6 +25,7 @@ import org.neo4j.driver.Statement; import org.neo4j.driver.internal.async.ExplicitTransaction; import org.neo4j.driver.internal.handlers.RunResponseHandler; +import org.neo4j.driver.internal.handlers.TransactionPullResponseCompletionListener; import org.neo4j.driver.internal.messaging.v4.BoltProtocolV4; import org.neo4j.driver.internal.spi.Connection; import org.neo4j.driver.summary.ResultSummary; @@ -35,38 +36,38 @@ import static org.mockito.ArgumentMatchers.eq; import static org.mockito.Mockito.mock; import static org.mockito.Mockito.verify; -import static org.neo4j.driver.internal.handlers.pulln.BasicPullResponseHandler.Status.DONE; -import static org.neo4j.driver.internal.handlers.pulln.BasicPullResponseHandler.Status.FAILED; +import static org.neo4j.driver.internal.handlers.pulln.PullResponseHandler.Status.FAILED; +import static org.neo4j.driver.internal.handlers.pulln.PullResponseHandler.Status.SUCCEEDED; -public class TransactionPullResponseHandlerTest extends AbstractBasicPullResponseHandlerTestBase +public class TransactionPullResponseCompletionListenerTest extends BasicPullResponseHandlerTestBase { @Override - protected void shouldHandleSuccessWithSummary( BasicPullResponseHandler.Status status ) + protected void shouldHandleSuccessWithSummary( PullResponseHandler.Status status ) { // Given Connection conn = mockConnection(); BiConsumer recordConsumer = mock( BiConsumer.class ); BiConsumer summaryConsumer = mock( BiConsumer.class ); - AbstractBasicPullResponseHandler handler = newResponseHandlerWithStatus( conn, recordConsumer, summaryConsumer, status); + BasicPullResponseHandler handler = newResponseHandlerWithStatus( conn, recordConsumer, summaryConsumer, status); // When handler.onSuccess( Collections.emptyMap() ); // Then - assertThat( handler.status(), equalTo( DONE ) ); + assertThat( handler.status(), equalTo( SUCCEEDED ) ); verify( recordConsumer ).accept( null, null ); verify( summaryConsumer ).accept( any( ResultSummary.class ), eq( null ) ); } @Override - protected void shouldHandleFailure( BasicPullResponseHandler.Status status ) + protected void shouldHandleFailure( PullResponseHandler.Status status ) { // Given Connection conn = mockConnection(); BiConsumer recordConsumer = mock( BiConsumer.class ); BiConsumer summaryConsumer = mock( BiConsumer.class ); ExplicitTransaction tx = mock( ExplicitTransaction.class ); - TransactionPullResponseHandler handler = newTxResponseHandler( conn, recordConsumer, summaryConsumer, tx, status ); + BasicPullResponseHandler handler = newTxResponseHandler( conn, recordConsumer, summaryConsumer, tx, status ); // When RuntimeException error = new RuntimeException( "I am an error" ); @@ -76,23 +77,24 @@ protected void shouldHandleFailure( BasicPullResponseHandler.Status status ) assertThat( handler.status(), equalTo( FAILED ) ); verify( tx ).markTerminated(); verify( recordConsumer ).accept( null, error ); - verify( summaryConsumer ).accept( any( ResultSummary.class ), eq( null ) ); + verify( summaryConsumer ).accept( any( ResultSummary.class ), eq( error ) ); } @Override - protected AbstractBasicPullResponseHandler newResponseHandlerWithStatus( Connection conn, BiConsumer recordConsumer, - BiConsumer summaryConsumer, BasicPullResponseHandler.Status status ) + protected BasicPullResponseHandler newResponseHandlerWithStatus( Connection conn, BiConsumer recordConsumer, + BiConsumer summaryConsumer, PullResponseHandler.Status status ) { ExplicitTransaction tx = mock( ExplicitTransaction.class ); return newTxResponseHandler( conn, recordConsumer, summaryConsumer, tx, status ); } - private static TransactionPullResponseHandler newTxResponseHandler( Connection conn, BiConsumer recordConsumer, - BiConsumer summaryConsumer, ExplicitTransaction tx, BasicPullResponseHandler.Status status ) + private static BasicPullResponseHandler newTxResponseHandler( Connection conn, BiConsumer recordConsumer, + BiConsumer summaryConsumer, ExplicitTransaction tx, PullResponseHandler.Status status ) { RunResponseHandler runHandler = mock( RunResponseHandler.class ); - TransactionPullResponseHandler handler = - new TransactionPullResponseHandler( mock( Statement.class ), runHandler, conn, tx, BoltProtocolV4.METADATA_EXTRACTOR ); + TransactionPullResponseCompletionListener listener = new TransactionPullResponseCompletionListener( tx ); + BasicPullResponseHandler handler = + new BasicPullResponseHandler( mock( Statement.class ), runHandler, conn, BoltProtocolV4.METADATA_EXTRACTOR, listener ); handler.installRecordConsumer( recordConsumer ); handler.installSummaryConsumer( summaryConsumer ); diff --git a/driver/src/test/java/org/neo4j/driver/internal/messaging/v1/BoltProtocolV1Test.java b/driver/src/test/java/org/neo4j/driver/internal/messaging/v1/BoltProtocolV1Test.java index 0f9110e7c1..345cc1b5b2 100644 --- a/driver/src/test/java/org/neo4j/driver/internal/messaging/v1/BoltProtocolV1Test.java +++ b/driver/src/test/java/org/neo4j/driver/internal/messaging/v1/BoltProtocolV1Test.java @@ -41,14 +41,13 @@ import org.neo4j.driver.internal.async.ExplicitTransaction; import org.neo4j.driver.internal.async.connection.ChannelAttributes; import org.neo4j.driver.internal.async.inbound.InboundMessageDispatcher; -import org.neo4j.driver.internal.cursor.InternalStatementResultCursor; +import org.neo4j.driver.internal.cursor.AsyncStatementResultCursor; import org.neo4j.driver.internal.handlers.BeginTxResponseHandler; import org.neo4j.driver.internal.handlers.CommitTxResponseHandler; import org.neo4j.driver.internal.handlers.NoOpResponseHandler; +import org.neo4j.driver.internal.handlers.PullAllResponseHandler; import org.neo4j.driver.internal.handlers.RollbackTxResponseHandler; import org.neo4j.driver.internal.handlers.RunResponseHandler; -import org.neo4j.driver.internal.handlers.SessionPullAllResponseHandler; -import org.neo4j.driver.internal.handlers.TransactionPullAllResponseHandler; import org.neo4j.driver.internal.messaging.BoltProtocol; import org.neo4j.driver.internal.messaging.MessageFormat; import org.neo4j.driver.internal.messaging.request.InitMessage; @@ -78,6 +77,7 @@ import static org.mockito.Mockito.when; import static org.neo4j.driver.Values.value; import static org.neo4j.driver.internal.DatabaseNameUtil.defaultDatabase; +import static org.neo4j.driver.internal.handlers.pulln.FetchSizeUtil.UNLIMITED_FETCH_SIZE; import static org.neo4j.driver.internal.messaging.v1.BoltProtocolV1.SingleBookmarkHelper.asBeginTransactionParameters; import static org.neo4j.driver.internal.util.Futures.blockingGet; import static org.neo4j.driver.util.TestUtil.await; @@ -272,7 +272,8 @@ void shouldNotSupportTransactionConfigForAutoCommitTransactions() .build(); ClientException e = assertThrows( ClientException.class, - () -> protocol.runInAutoCommitTransaction( connectionMock( protocol ), new Statement( "RETURN 1" ), BookmarkHolder.NO_OP, config, true ) ); + () -> protocol.runInAutoCommitTransaction( connectionMock( protocol ), new Statement( "RETURN 1" ), BookmarkHolder.NO_OP, config, true, + UNLIMITED_FETCH_SIZE ) ); assertThat( e.getMessage(), startsWith( "Driver is connected to the database that does not support transaction configuration" ) ); } @@ -290,7 +291,7 @@ void shouldNotSupportDatabaseNameForAutoCommitTransactions() { ClientException e = assertThrows( ClientException.class, () -> protocol.runInAutoCommitTransaction( connectionMock( "foo", protocol ), - new Statement( "RETURN 1" ), BookmarkHolder.NO_OP, TransactionConfig.empty(), true ) ); + new Statement( "RETURN 1" ), BookmarkHolder.NO_OP, TransactionConfig.empty(), true, UNLIMITED_FETCH_SIZE ) ); assertThat( e.getMessage(), startsWith( "Database name parameter for selecting database is not supported" ) ); } @@ -309,24 +310,24 @@ private void testRunWithoutWaitingForRunResponse( boolean autoCommitTx ) throws Connection connection = mock( Connection.class ); when( connection.databaseName() ).thenReturn( defaultDatabase() ); - CompletionStage cursorStage; + CompletionStage cursorStage; if ( autoCommitTx ) { cursorStage = protocol - .runInAutoCommitTransaction( connection, STATEMENT, BookmarkHolder.NO_OP, TransactionConfig.empty(), false ) + .runInAutoCommitTransaction( connection, STATEMENT, BookmarkHolder.NO_OP, TransactionConfig.empty(), false, UNLIMITED_FETCH_SIZE ) .asyncResult(); } else { cursorStage = protocol - .runInExplicitTransaction( connection, STATEMENT, mock( ExplicitTransaction.class ), false ) + .runInExplicitTransaction( connection, STATEMENT, mock( ExplicitTransaction.class ), false, UNLIMITED_FETCH_SIZE ) .asyncResult(); } - CompletableFuture cursorFuture = cursorStage.toCompletableFuture(); + CompletableFuture cursorFuture = cursorStage.toCompletableFuture(); assertTrue( cursorFuture.isDone() ); assertNotNull( cursorFuture.get() ); - verifyRunInvoked( connection, autoCommitTx ); + verifyRunInvoked( connection ); } private void testRunWithWaitingForResponse( boolean success, boolean session ) throws Exception @@ -334,21 +335,21 @@ private void testRunWithWaitingForResponse( boolean success, boolean session ) t Connection connection = mock( Connection.class ); when( connection.databaseName() ).thenReturn( defaultDatabase() ); - CompletionStage cursorStage; + CompletionStage cursorStage; if ( session ) { - cursorStage = protocol.runInAutoCommitTransaction( connection, STATEMENT, BookmarkHolder.NO_OP, TransactionConfig.empty(), true ) + cursorStage = protocol.runInAutoCommitTransaction( connection, STATEMENT, BookmarkHolder.NO_OP, TransactionConfig.empty(), true, UNLIMITED_FETCH_SIZE ) .asyncResult(); } else { - cursorStage = protocol.runInExplicitTransaction( connection, STATEMENT, mock( ExplicitTransaction.class ), true ) + cursorStage = protocol.runInExplicitTransaction( connection, STATEMENT, mock( ExplicitTransaction.class ), true, UNLIMITED_FETCH_SIZE ) .asyncResult(); } - CompletableFuture cursorFuture = cursorStage.toCompletableFuture(); + CompletableFuture cursorFuture = cursorStage.toCompletableFuture(); assertFalse( cursorFuture.isDone() ); - ResponseHandler runResponseHandler = verifyRunInvoked( connection, session ); + ResponseHandler runResponseHandler = verifyRunInvoked( connection ); if ( success ) { @@ -363,24 +364,16 @@ private void testRunWithWaitingForResponse( boolean success, boolean session ) t assertNotNull( cursorFuture.get() ); } - private static ResponseHandler verifyRunInvoked( Connection connection, boolean session ) + private static ResponseHandler verifyRunInvoked( Connection connection ) { ArgumentCaptor runHandlerCaptor = ArgumentCaptor.forClass( ResponseHandler.class ); ArgumentCaptor pullAllHandlerCaptor = ArgumentCaptor.forClass( ResponseHandler.class ); - verify( connection ).writeAndFlush( eq( new RunMessage( QUERY, PARAMS ) ), runHandlerCaptor.capture(), - eq( PullAllMessage.PULL_ALL ), pullAllHandlerCaptor.capture() ); + verify( connection ).write( eq( new RunMessage( QUERY, PARAMS ) ), runHandlerCaptor.capture() ); + verify( connection ).writeAndFlush( eq( PullAllMessage.PULL_ALL ), pullAllHandlerCaptor.capture() ); assertThat( runHandlerCaptor.getValue(), instanceOf( RunResponseHandler.class ) ); - - if ( session ) - { - assertThat( pullAllHandlerCaptor.getValue(), instanceOf( SessionPullAllResponseHandler.class ) ); - } - else - { - assertThat( pullAllHandlerCaptor.getValue(), instanceOf( TransactionPullAllResponseHandler.class ) ); - } + assertThat( pullAllHandlerCaptor.getValue(), instanceOf( PullAllResponseHandler.class ) ); return runHandlerCaptor.getValue(); } diff --git a/driver/src/test/java/org/neo4j/driver/internal/messaging/v3/BoltProtocolV3Test.java b/driver/src/test/java/org/neo4j/driver/internal/messaging/v3/BoltProtocolV3Test.java index 483c424f99..121639ed7b 100644 --- a/driver/src/test/java/org/neo4j/driver/internal/messaging/v3/BoltProtocolV3Test.java +++ b/driver/src/test/java/org/neo4j/driver/internal/messaging/v3/BoltProtocolV3Test.java @@ -44,14 +44,13 @@ import org.neo4j.driver.internal.async.ExplicitTransaction; import org.neo4j.driver.internal.async.connection.ChannelAttributes; import org.neo4j.driver.internal.async.inbound.InboundMessageDispatcher; -import org.neo4j.driver.internal.cursor.InternalStatementResultCursor; +import org.neo4j.driver.internal.cursor.AsyncStatementResultCursor; import org.neo4j.driver.internal.handlers.BeginTxResponseHandler; import org.neo4j.driver.internal.handlers.CommitTxResponseHandler; import org.neo4j.driver.internal.handlers.NoOpResponseHandler; +import org.neo4j.driver.internal.handlers.PullAllResponseHandler; import org.neo4j.driver.internal.handlers.RollbackTxResponseHandler; import org.neo4j.driver.internal.handlers.RunResponseHandler; -import org.neo4j.driver.internal.handlers.SessionPullAllResponseHandler; -import org.neo4j.driver.internal.handlers.TransactionPullAllResponseHandler; import org.neo4j.driver.internal.messaging.BoltProtocol; import org.neo4j.driver.internal.messaging.MessageFormat; import org.neo4j.driver.internal.messaging.request.BeginMessage; @@ -86,6 +85,7 @@ import static org.neo4j.driver.AccessMode.WRITE; import static org.neo4j.driver.Values.value; import static org.neo4j.driver.internal.DatabaseNameUtil.defaultDatabase; +import static org.neo4j.driver.internal.handlers.pulln.FetchSizeUtil.UNLIMITED_FETCH_SIZE; import static org.neo4j.driver.util.TestUtil.anyServerVersion; import static org.neo4j.driver.util.TestUtil.await; import static org.neo4j.driver.util.TestUtil.connectionMock; @@ -342,7 +342,7 @@ protected void testDatabaseNameSupport( boolean autoCommitTx ) { e = assertThrows( ClientException.class, () -> protocol.runInAutoCommitTransaction( connectionMock( "foo", protocol ), new Statement( "RETURN 1" ), BookmarkHolder.NO_OP, - TransactionConfig.empty(), true ) ); + TransactionConfig.empty(), true, UNLIMITED_FETCH_SIZE ) ); } else { @@ -358,8 +358,8 @@ protected void testRunInExplicitTransactionAndWaitForRunResponse( boolean succes // Given Connection connection = connectionMock( mode, protocol ); - CompletableFuture cursorFuture = - protocol.runInExplicitTransaction( connection, STATEMENT, mock( ExplicitTransaction.class ), true ).asyncResult().toCompletableFuture(); + CompletableFuture cursorFuture = + protocol.runInExplicitTransaction( connection, STATEMENT, mock( ExplicitTransaction.class ), true, UNLIMITED_FETCH_SIZE ).asyncResult().toCompletableFuture(); ResponseHandler runResponseHandler = verifyRunInvoked( connection, false, InternalBookmark.empty(), TransactionConfig.empty(), mode ).runHandler; assertFalse( cursorFuture.isDone() ); @@ -384,17 +384,17 @@ protected void testRunWithoutWaitingForRunResponse( boolean autoCommitTx, Transa Connection connection = connectionMock( mode, protocol ); InternalBookmark initialBookmark = InternalBookmark.parse( "neo4j:bookmark:v1:tx987" ); - CompletionStage cursorStage; + CompletionStage cursorStage; if ( autoCommitTx ) { BookmarkHolder bookmarkHolder = new DefaultBookmarkHolder( initialBookmark ); - cursorStage = protocol.runInAutoCommitTransaction( connection, STATEMENT, bookmarkHolder, config, false ).asyncResult(); + cursorStage = protocol.runInAutoCommitTransaction( connection, STATEMENT, bookmarkHolder, config, false, UNLIMITED_FETCH_SIZE ).asyncResult(); } else { - cursorStage = protocol.runInExplicitTransaction( connection, STATEMENT, mock( ExplicitTransaction.class ), false ).asyncResult(); + cursorStage = protocol.runInExplicitTransaction( connection, STATEMENT, mock( ExplicitTransaction.class ), false, UNLIMITED_FETCH_SIZE ).asyncResult(); } - CompletableFuture cursorFuture = cursorStage.toCompletableFuture(); + CompletableFuture cursorFuture = cursorStage.toCompletableFuture(); assertTrue( cursorFuture.isDone() ); assertNotNull( cursorFuture.get() ); @@ -414,8 +414,10 @@ protected void testSuccessfulRunInAutoCommitTxWithWaitingForResponse( InternalBo Connection connection = connectionMock( mode, protocol ); BookmarkHolder bookmarkHolder = new DefaultBookmarkHolder( bookmark ); - CompletableFuture cursorFuture = - protocol.runInAutoCommitTransaction( connection, STATEMENT, bookmarkHolder, config, true ).asyncResult().toCompletableFuture(); + CompletableFuture cursorFuture = + protocol.runInAutoCommitTransaction( connection, STATEMENT, bookmarkHolder, config, true, UNLIMITED_FETCH_SIZE ) + .asyncResult() + .toCompletableFuture(); assertFalse( cursorFuture.isDone() ); ResponseHandlers handlers = verifyRunInvoked( connection, true, bookmark, config, mode ); @@ -434,8 +436,10 @@ protected void testFailedRunInAutoCommitTxWithWaitingForResponse( InternalBookma Connection connection = connectionMock( mode, protocol ); BookmarkHolder bookmarkHolder = new DefaultBookmarkHolder( bookmark ); - CompletableFuture cursorFuture = - protocol.runInAutoCommitTransaction( connection, STATEMENT, bookmarkHolder, config, true ).asyncResult().toCompletableFuture(); + CompletableFuture cursorFuture = + protocol.runInAutoCommitTransaction( connection, STATEMENT, bookmarkHolder, config, true, UNLIMITED_FETCH_SIZE ) + .asyncResult() + .toCompletableFuture(); assertFalse( cursorFuture.isDone() ); ResponseHandler runResponseHandler = verifyRunInvoked( connection, true, bookmark, config, mode ).runHandler; @@ -469,19 +473,11 @@ private static ResponseHandlers verifyRunInvoked( Connection connection, boolean expectedMessage = RunWithMetadataMessage.explicitTxRunMessage( STATEMENT ); } - verify( connection ).writeAndFlush( eq( expectedMessage ), runHandlerCaptor.capture(), - eq( PullAllMessage.PULL_ALL ), pullAllHandlerCaptor.capture() ); + verify( connection ).write( eq( expectedMessage ), runHandlerCaptor.capture() ); + verify( connection ).writeAndFlush( eq( PullAllMessage.PULL_ALL ), pullAllHandlerCaptor.capture() ); assertThat( runHandlerCaptor.getValue(), instanceOf( RunResponseHandler.class ) ); - - if ( session ) - { - assertThat( pullAllHandlerCaptor.getValue(), instanceOf( SessionPullAllResponseHandler.class ) ); - } - else - { - assertThat( pullAllHandlerCaptor.getValue(), instanceOf( TransactionPullAllResponseHandler.class ) ); - } + assertThat( pullAllHandlerCaptor.getValue(), instanceOf( PullAllResponseHandler.class ) ); return new ResponseHandlers( runHandlerCaptor.getValue(), pullAllHandlerCaptor.getValue() ); } diff --git a/driver/src/test/java/org/neo4j/driver/internal/messaging/v4/BoltProtocolV4Test.java b/driver/src/test/java/org/neo4j/driver/internal/messaging/v4/BoltProtocolV4Test.java index 8aa8471ee5..fd6f511102 100644 --- a/driver/src/test/java/org/neo4j/driver/internal/messaging/v4/BoltProtocolV4Test.java +++ b/driver/src/test/java/org/neo4j/driver/internal/messaging/v4/BoltProtocolV4Test.java @@ -30,7 +30,7 @@ import org.neo4j.driver.internal.DefaultBookmarkHolder; import org.neo4j.driver.internal.InternalBookmark; import org.neo4j.driver.internal.async.ExplicitTransaction; -import org.neo4j.driver.internal.cursor.InternalStatementResultCursor; +import org.neo4j.driver.internal.cursor.AsyncStatementResultCursor; import org.neo4j.driver.internal.cursor.StatementResultCursorFactory; import org.neo4j.driver.internal.handlers.BeginTxResponseHandler; import org.neo4j.driver.internal.handlers.NoOpResponseHandler; @@ -52,11 +52,13 @@ import static org.junit.jupiter.api.Assertions.assertFalse; import static org.junit.jupiter.api.Assertions.assertNotNull; import static org.junit.jupiter.api.Assertions.assertTrue; +import static org.mockito.ArgumentMatchers.any; import static org.mockito.ArgumentMatchers.eq; import static org.mockito.Mockito.mock; import static org.mockito.Mockito.verify; import static org.neo4j.driver.internal.DatabaseNameUtil.database; import static org.neo4j.driver.internal.DatabaseNameUtil.defaultDatabase; +import static org.neo4j.driver.internal.handlers.pulln.FetchSizeUtil.UNLIMITED_FETCH_SIZE; import static org.neo4j.driver.util.TestUtil.await; import static org.neo4j.driver.util.TestUtil.connectionMock; @@ -81,8 +83,10 @@ protected void testFailedRunInAutoCommitTxWithWaitingForResponse( InternalBookma Connection connection = connectionMock( mode, protocol ); BookmarkHolder bookmarkHolder = new DefaultBookmarkHolder( bookmark ); - CompletableFuture cursorFuture = - protocol.runInAutoCommitTransaction( connection, STATEMENT, bookmarkHolder, config, true ).asyncResult().toCompletableFuture(); + CompletableFuture cursorFuture = + protocol.runInAutoCommitTransaction( connection, STATEMENT, bookmarkHolder, config, true, UNLIMITED_FETCH_SIZE ) + .asyncResult() + .toCompletableFuture(); ResponseHandler runHandler = verifySessionRunInvoked( connection, bookmark, config, mode, defaultDatabase() ); assertFalse( cursorFuture.isDone() ); @@ -103,8 +107,10 @@ protected void testSuccessfulRunInAutoCommitTxWithWaitingForResponse( InternalBo Connection connection = connectionMock( mode, protocol ); BookmarkHolder bookmarkHolder = new DefaultBookmarkHolder( bookmark ); - CompletableFuture cursorFuture = - protocol.runInAutoCommitTransaction( connection, STATEMENT, bookmarkHolder, config, true ).asyncResult().toCompletableFuture(); + CompletableFuture cursorFuture = + protocol.runInAutoCommitTransaction( connection, STATEMENT, bookmarkHolder, config, true, UNLIMITED_FETCH_SIZE ) + .asyncResult() + .toCompletableFuture(); ResponseHandler runHandler = verifySessionRunInvoked( connection, bookmark, config, mode, defaultDatabase() ); assertFalse( cursorFuture.isDone() ); @@ -124,8 +130,10 @@ protected void testRunInExplicitTransactionAndWaitForRunResponse( boolean succes // Given Connection connection = connectionMock( mode, protocol ); - CompletableFuture cursorFuture = - protocol.runInExplicitTransaction( connection, STATEMENT, mock( ExplicitTransaction.class ), true ).asyncResult().toCompletableFuture(); + CompletableFuture cursorFuture = + protocol.runInExplicitTransaction( connection, STATEMENT, mock( ExplicitTransaction.class ), true, UNLIMITED_FETCH_SIZE ) + .asyncResult() + .toCompletableFuture(); ResponseHandler runHandler = verifyTxRunInvoked( connection ); assertFalse( cursorFuture.isDone() ); @@ -152,19 +160,21 @@ protected void testRunWithoutWaitingForRunResponse( boolean autoCommitTx, Transa Connection connection = connectionMock( mode, protocol ); InternalBookmark initialBookmark = InternalBookmark.parse( "neo4j:bookmark:v1:tx987" ); - CompletionStage cursorStage; + CompletionStage cursorStage; if ( autoCommitTx ) { BookmarkHolder bookmarkHolder = new DefaultBookmarkHolder( initialBookmark ); - cursorStage = protocol.runInAutoCommitTransaction( connection, STATEMENT, bookmarkHolder, config, false ).asyncResult(); + cursorStage = protocol.runInAutoCommitTransaction( connection, STATEMENT, bookmarkHolder, config, false, UNLIMITED_FETCH_SIZE ) + .asyncResult(); } else { - cursorStage = protocol.runInExplicitTransaction( connection, STATEMENT, mock( ExplicitTransaction.class ), false ).asyncResult(); + cursorStage = protocol.runInExplicitTransaction( connection, STATEMENT, mock( ExplicitTransaction.class ), false, UNLIMITED_FETCH_SIZE ) + .asyncResult(); } // When I complete it immediately without waiting for any responses to run message - CompletableFuture cursorFuture = cursorStage.toCompletableFuture(); + CompletableFuture cursorFuture = cursorStage.toCompletableFuture(); assertTrue( cursorFuture.isDone() ); assertNotNull( cursorFuture.get() ); @@ -186,7 +196,7 @@ protected void testDatabaseNameSupport( boolean autoCommitTx ) if ( autoCommitTx ) { StatementResultCursorFactory factory = - protocol.runInAutoCommitTransaction( connection, STATEMENT, BookmarkHolder.NO_OP, TransactionConfig.empty(), false ); + protocol.runInAutoCommitTransaction( connection, STATEMENT, BookmarkHolder.NO_OP, TransactionConfig.empty(), false, UNLIMITED_FETCH_SIZE ); await( factory.asyncResult() ); verifySessionRunInvoked( connection, InternalBookmark.empty(), TransactionConfig.empty(), AccessMode.WRITE, database( "foo" ) ); } @@ -214,7 +224,8 @@ private ResponseHandler verifyRunInvoked( Connection connection, RunWithMetadata ArgumentCaptor runHandlerCaptor = ArgumentCaptor.forClass( ResponseHandler.class ); ArgumentCaptor pullHandlerCaptor = ArgumentCaptor.forClass( ResponseHandler.class ); - verify( connection ).writeAndFlush( eq( runMessage ), runHandlerCaptor.capture(), eq( PullMessage.PULL_ALL ), pullHandlerCaptor.capture() ); + verify( connection ).write( eq( runMessage ), runHandlerCaptor.capture() ); + verify( connection ).writeAndFlush( any( PullMessage.class ), pullHandlerCaptor.capture() ); assertThat( runHandlerCaptor.getValue(), instanceOf( RunResponseHandler.class ) ); assertThat( pullHandlerCaptor.getValue(), instanceOf( PullAllResponseHandler.class ) ); diff --git a/driver/src/test/java/org/neo4j/driver/internal/reactive/InternalRxSessionTest.java b/driver/src/test/java/org/neo4j/driver/internal/reactive/InternalRxSessionTest.java index 95f60df326..649f1fda7b 100644 --- a/driver/src/test/java/org/neo4j/driver/internal/reactive/InternalRxSessionTest.java +++ b/driver/src/test/java/org/neo4j/driver/internal/reactive/InternalRxSessionTest.java @@ -41,6 +41,7 @@ import org.neo4j.driver.internal.async.ExplicitTransaction; import org.neo4j.driver.internal.async.NetworkSession; import org.neo4j.driver.internal.cursor.RxStatementResultCursor; +import org.neo4j.driver.internal.cursor.RxStatementResultCursorImpl; import org.neo4j.driver.internal.util.FixedRetryLogic; import org.neo4j.driver.internal.util.Futures; import org.neo4j.driver.internal.value.IntegerValue; @@ -105,7 +106,7 @@ void shouldDelegateRun( Function runReturnOne ) thr { // Given NetworkSession session = mock( NetworkSession.class ); - RxStatementResultCursor cursor = mock( RxStatementResultCursor.class ); + RxStatementResultCursor cursor = mock( RxStatementResultCursorImpl.class ); // Run succeeded with a cursor when( session.runRx( any( Statement.class ), any( TransactionConfig.class ) ) ).thenReturn( completedFuture( cursor ) ); 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 34a4b51830..630138d29f 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 @@ -29,8 +29,9 @@ import java.util.concurrent.CompletionException; import org.neo4j.driver.internal.InternalRecord; +import org.neo4j.driver.internal.cursor.RxStatementResultCursorImpl; import org.neo4j.driver.internal.handlers.RunResponseHandler; -import org.neo4j.driver.internal.handlers.pulln.BasicPullResponseHandler; +import org.neo4j.driver.internal.handlers.pulln.PullResponseHandler; import org.neo4j.driver.internal.util.Futures; import org.neo4j.driver.reactive.RxStatementResult; import org.neo4j.driver.internal.reactive.util.ListBasedPullHandler; @@ -57,7 +58,7 @@ class InternalRxStatementResultTest void shouldInitCursorFuture() { // Given - RxStatementResultCursor cursor = mock( RxStatementResultCursor.class ); + RxStatementResultCursor cursor = mock( RxStatementResultCursorImpl.class ); InternalRxStatementResult rxResult = newRxResult( cursor ); // When @@ -88,7 +89,7 @@ void shouldInitCursorFutureWithFailedCursor() void shouldObtainKeys() { // Given - RxStatementResultCursor cursor = mock( RxStatementResultCursor.class ); + RxStatementResultCursor cursor = mock( RxStatementResultCursorImpl.class ); RxStatementResult rxResult = newRxResult( cursor ); List keys = Arrays.asList( "one", "two", "three" ); @@ -119,7 +120,7 @@ void shouldErrorWhenFailedObtainKeys() void shouldCancelKeys() { // Given - RxStatementResultCursor cursor = mock( RxStatementResultCursor.class ); + RxStatementResultCursor cursor = mock( RxStatementResultCursorImpl.class ); RxStatementResult rxResult = newRxResult( cursor ); List keys = Arrays.asList( "one", "two", "three" ); @@ -139,7 +140,7 @@ void shouldObtainRecordsAndSummary() Record record2 = new InternalRecord( asList( "key1", "key2", "key3" ), values( 2, 2, 2 ) ); Record record3 = new InternalRecord( asList( "key1", "key2", "key3" ), values( 3, 3, 3 ) ); - BasicPullResponseHandler pullHandler = new ListBasedPullHandler( Arrays.asList( record1, record2, record3 ) ); + PullResponseHandler pullHandler = new ListBasedPullHandler( Arrays.asList( record1, record2, record3 ) ); RxStatementResult rxResult = newRxResult( pullHandler ); // When @@ -159,7 +160,7 @@ void shouldCancelStreamingButObtainSummary() Record record2 = new InternalRecord( asList( "key1", "key2", "key3" ), values( 2, 2, 2 ) ); Record record3 = new InternalRecord( asList( "key1", "key2", "key3" ), values( 3, 3, 3 ) ); - BasicPullResponseHandler pullHandler = new ListBasedPullHandler( Arrays.asList( record1, record2, record3 ) ); + PullResponseHandler pullHandler = new ListBasedPullHandler( Arrays.asList( record1, record2, record3 ) ); RxStatementResult rxResult = newRxResult( pullHandler ); // When @@ -195,11 +196,11 @@ void shouldErrorIfFailedToStream() } ).verifyComplete(); } - private InternalRxStatementResult newRxResult( BasicPullResponseHandler pullHandler ) + private InternalRxStatementResult newRxResult( PullResponseHandler pullHandler ) { RunResponseHandler runHandler = mock( RunResponseHandler.class ); when( runHandler.runFuture() ).thenReturn( Futures.completedWithNull() ); - RxStatementResultCursor cursor = new RxStatementResultCursor( runHandler, pullHandler ); + RxStatementResultCursor cursor = new RxStatementResultCursorImpl( runHandler, pullHandler ); return newRxResult( cursor ); } diff --git a/driver/src/test/java/org/neo4j/driver/internal/reactive/InternalRxTransactionTest.java b/driver/src/test/java/org/neo4j/driver/internal/reactive/InternalRxTransactionTest.java index 02c28719cb..64d43ad34d 100644 --- a/driver/src/test/java/org/neo4j/driver/internal/reactive/InternalRxTransactionTest.java +++ b/driver/src/test/java/org/neo4j/driver/internal/reactive/InternalRxTransactionTest.java @@ -34,6 +34,7 @@ import org.neo4j.driver.internal.InternalRecord; import org.neo4j.driver.internal.async.ExplicitTransaction; import org.neo4j.driver.internal.cursor.RxStatementResultCursor; +import org.neo4j.driver.internal.cursor.RxStatementResultCursorImpl; import org.neo4j.driver.internal.util.Futures; import org.neo4j.driver.internal.value.IntegerValue; import org.neo4j.driver.reactive.RxStatementResult; @@ -97,7 +98,7 @@ void shouldDelegateRun( Function runReturnOne ) { // Given ExplicitTransaction tx = mock( ExplicitTransaction.class ); - RxStatementResultCursor cursor = mock( RxStatementResultCursor.class ); + RxStatementResultCursor cursor = mock( RxStatementResultCursorImpl.class ); // Run succeeded with a cursor when( tx.runRx( any( Statement.class ) ) ).thenReturn( completedFuture( cursor ) ); diff --git a/driver/src/test/java/org/neo4j/driver/internal/reactive/util/ListBasedPullHandler.java b/driver/src/test/java/org/neo4j/driver/internal/reactive/util/ListBasedPullHandler.java index c397c0e155..4cfb2f0737 100644 --- a/driver/src/test/java/org/neo4j/driver/internal/reactive/util/ListBasedPullHandler.java +++ b/driver/src/test/java/org/neo4j/driver/internal/reactive/util/ListBasedPullHandler.java @@ -21,8 +21,9 @@ import java.util.List; import java.util.Map; +import org.neo4j.driver.internal.handlers.PullResponseCompletionListener; import org.neo4j.driver.internal.handlers.RunResponseHandler; -import org.neo4j.driver.internal.handlers.pulln.AbstractBasicPullResponseHandler; +import org.neo4j.driver.internal.handlers.pulln.BasicPullResponseHandler; import org.neo4j.driver.internal.spi.Connection; import org.neo4j.driver.internal.util.MetadataExtractor; import org.neo4j.driver.internal.value.BooleanValue; @@ -39,7 +40,7 @@ import static org.mockito.Mockito.mock; import static org.mockito.Mockito.when; -public class ListBasedPullHandler extends AbstractBasicPullResponseHandler +public class ListBasedPullHandler extends BasicPullResponseHandler { private final List list; private final Throwable error; @@ -62,7 +63,8 @@ public ListBasedPullHandler( Throwable error ) private ListBasedPullHandler( List list, Throwable error ) { - super( mock( Statement.class ), mock( RunResponseHandler.class ), mock( Connection.class ), mock( MetadataExtractor.class ) ); + super( mock( Statement.class ), mock( RunResponseHandler.class ), mock( Connection.class ), mock( MetadataExtractor.class ), mock( + PullResponseCompletionListener.class ) ); this.list = list; this.error = error; when( super.metadataExtractor.extractSummary( any( Statement.class ), any( Connection.class ), anyLong(), any( Map.class ) ) ).thenReturn( @@ -74,16 +76,6 @@ private ListBasedPullHandler( List list, Throwable error ) } } - @Override - protected void afterSuccess( Map metadata ) - { - } - - @Override - protected void afterFailure( Throwable error ) - { - } - @Override public void request( long n ) { diff --git a/driver/src/test/java/org/neo4j/driver/internal/util/FailingConnectionDriverFactory.java b/driver/src/test/java/org/neo4j/driver/internal/util/FailingConnectionDriverFactory.java index c0c1aa17a1..f30f820a72 100644 --- a/driver/src/test/java/org/neo4j/driver/internal/util/FailingConnectionDriverFactory.java +++ b/driver/src/test/java/org/neo4j/driver/internal/util/FailingConnectionDriverFactory.java @@ -22,6 +22,7 @@ import java.util.Set; import java.util.concurrent.CompletionStage; +import java.util.concurrent.atomic.AtomicInteger; import java.util.concurrent.atomic.AtomicReference; import org.neo4j.driver.internal.BoltServerAddress; @@ -106,6 +107,7 @@ private static class FailingConnection implements Connection { final Connection delegate; final AtomicReference nextRunFailure; + final AtomicInteger count = new AtomicInteger( 2 ); // one failure for run, one failure for pull FailingConnection( Connection delegate, AtomicReference nextRunFailure ) { @@ -222,13 +224,21 @@ private boolean tryFail( ResponseHandler handler1, ResponseHandler handler2 ) Throwable failure = nextRunFailure.getAndSet( null ); if ( failure != null ) { + int reportCount = count.get(); if ( handler1 != null ) { handler1.onFailure( failure ); + reportCount = count.decrementAndGet(); } if ( handler2 != null ) { handler2.onFailure( failure ); + reportCount = count.decrementAndGet(); + } + + if ( reportCount > 0 ) + { + nextRunFailure.compareAndSet( null, failure ); } return true; } 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 0d4da24e09..1661b7fad4 100644 --- a/driver/src/test/java/org/neo4j/driver/stress/AbstractStressTestBase.java +++ b/driver/src/test/java/org/neo4j/driver/stress/AbstractStressTestBase.java @@ -130,7 +130,6 @@ void tearDown() if ( driver != null ) { driver.close(); - System.out.println( driver.metrics() ); } } @@ -683,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 ).consume(); + tx.run( statement ).summary(); } private static CompletionStage createNodesInTxAsync( AsyncTransaction tx, int batchIndex, int batchSize ) @@ -703,7 +702,7 @@ private static CompletableFuture createNodeInTxAsync( AsyncTransaction tx, { Statement statement = createNodeInTxStatement( nodeIndex ); return tx.runAsync( statement ) - .thenCompose( StatementResultCursor::consumeAsync ) + .thenCompose( StatementResultCursor::summaryAsync ) .thenApply( ignore -> (Void) null ) .toCompletableFuture(); } 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 7864464de4..897987fbb4 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::consume ); + Exception e = assertThrows( Exception.class, result::summary ); 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 2b8b7e55ec..5560242f81 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::consume ); + Exception e = assertThrows( Exception.class, result::summary ); assertThat( e, is( arithmeticError() ) ); } } 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 5431662b5f..3294e882f2 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" ).consume() ); + Exception e = assertThrows( Exception.class, () -> session.run( "RETURN" ).summary() ); 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 f5438b1769..d18001f64e 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" ).consume() ); + Exception e = assertThrows( Exception.class, () -> tx.run( "RETURN" ).summary() ); 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 416e9e1605..ba38a42f82 100644 --- a/driver/src/test/java/org/neo4j/driver/stress/CausalClusteringIT.java +++ b/driver/src/test/java/org/neo4j/driver/stress/CausalClusteringIT.java @@ -42,6 +42,7 @@ import org.neo4j.driver.AccessMode; import org.neo4j.driver.AuthToken; +import org.neo4j.driver.Bookmark; import org.neo4j.driver.Config; import org.neo4j.driver.Driver; import org.neo4j.driver.GraphDatabase; @@ -59,7 +60,6 @@ import org.neo4j.driver.exceptions.SessionExpiredException; import org.neo4j.driver.integration.NestedQueries; import org.neo4j.driver.internal.BoltServerAddress; -import org.neo4j.driver.Bookmark; import org.neo4j.driver.internal.cluster.RoutingSettings; import org.neo4j.driver.internal.retry.RetrySettings; import org.neo4j.driver.internal.util.DisabledOnNeo4jWith; @@ -89,8 +89,9 @@ import static org.junit.jupiter.api.Assertions.assertNotNull; import static org.junit.jupiter.api.Assertions.assertThrows; import static org.junit.jupiter.api.Assertions.assertTrue; -import static org.neo4j.driver.Values.parameters; +import static org.neo4j.driver.Logging.none; import static org.neo4j.driver.SessionConfig.builder; +import static org.neo4j.driver.Values.parameters; import static org.neo4j.driver.internal.InternalBookmark.parse; import static org.neo4j.driver.internal.logging.DevNullLogging.DEV_NULL_LOGGING; import static org.neo4j.driver.internal.util.Matchers.connectionAcquisitionTimeoutError; @@ -387,7 +388,7 @@ void shouldNotServeWritesWhenMajorityOfCoresAreDead() { try ( Session session = driver.session( builder().withDefaultAccessMode( AccessMode.WRITE ).build() ) ) { - session.run( "CREATE (p:Person {name: 'Gamora'})" ).consume(); + session.run( "CREATE (p:Person {name: 'Gamora'})" ).summary(); } } ); } @@ -429,7 +430,7 @@ void shouldServeReadsWhenMajorityOfCoresAreDead() { try ( Session session = driver.session( builder().withDefaultAccessMode( AccessMode.WRITE ).build() ) ) { - session.run( "CREATE (p:Person {name: 'Gamora'})" ).consume(); + session.run( "CREATE (p:Person {name: 'Gamora'})" ).summary(); } } ); @@ -496,8 +497,8 @@ void shouldNotReuseReadConnectionForWriteTransaction() CompletionStage> resultsStage = session.runAsync( "RETURN 42" ) .thenCompose( cursor1 -> - session.writeTransactionAsync( tx -> tx.runAsync( "CREATE (:Node1) RETURN 42" ) ) - .thenCompose( cursor2 -> combineCursors( cursor2, cursor1 ) ) ); + session.writeTransactionAsync( tx -> tx.runAsync( "CREATE (:Node1) RETURN 42" ) + .thenCompose( cursor2 -> combineCursors( cursor2, cursor1 ) ) ) ); List results = await( resultsStage ); assertEquals( 2, results.size() ); @@ -568,11 +569,11 @@ RoutingSettings.DEFAULT, RetrySettings.DEFAULT, configWithoutLogging() ) ) { Session session1 = driver.session(); Transaction tx1 = session1.beginTransaction(); - tx1.run( "CREATE (n:Node1 {name: 'Node1'})" ).consume(); + tx1.run( "CREATE (n:Node1 {name: 'Node1'})" ).summary(); Session session2 = driver.session(); Transaction tx2 = session2.beginTransaction(); - tx2.run( "CREATE (n:Node2 {name: 'Node2'})" ).consume(); + tx2.run( "CREATE (n:Node2 {name: 'Node2'})" ).summary(); ServiceUnavailableException error = new ServiceUnavailableException( "Connection broke!" ); driverFactory.setNextRunFailure( error ); @@ -627,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" ).consume() ); + () -> runCreateNode( session, "Person", "name", "Vision" ).summary() ); assertEquals( "Disconnected", e.getCause().getMessage() ); } @@ -707,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'})" ).consume() ); + SessionExpiredException e = assertThrows( SessionExpiredException.class, () -> tx.run( "CREATE (n:Node3 {name: 'Node3'})" ).summary() ); assertEquals( cause, e.getCause() ); } @@ -754,7 +755,7 @@ private Function executeWriteAndRead() { return session -> { - session.run( "MERGE (n:Person {name: 'Jim'})" ).consume(); + session.run( "MERGE (n:Person {name: 'Jim'})" ).summary(); Record record = session.run( "MATCH (n:Person) RETURN COUNT(*) AS count" ).next(); return record.get( "count" ).asInt(); }; @@ -1018,7 +1019,7 @@ private static int runCountNodes( StatementRunner statementRunner, String label, private static Config configWithoutLogging() { - return Config.builder().withLogging( DEV_NULL_LOGGING ).build(); + return Config.builder().withLogging( none() ).build(); } private static ExecutorService newExecutor() 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 7deeb20eec..fe6e20e164 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.consume(); + run.summary(); Thread.sleep( random.nextInt( 100 ) ); } } diff --git a/driver/src/test/java/org/neo4j/driver/util/TestUtil.java b/driver/src/test/java/org/neo4j/driver/util/TestUtil.java index 102d42b352..be451c2053 100644 --- a/driver/src/test/java/org/neo4j/driver/util/TestUtil.java +++ b/driver/src/test/java/org/neo4j/driver/util/TestUtil.java @@ -95,6 +95,7 @@ import static org.neo4j.driver.internal.DatabaseNameUtil.database; import static org.neo4j.driver.internal.DatabaseNameUtil.defaultDatabase; import static org.neo4j.driver.internal.InternalBookmark.empty; +import static org.neo4j.driver.internal.handlers.pulln.FetchSizeUtil.UNLIMITED_FETCH_SIZE; import static org.neo4j.driver.internal.logging.DevNullLogging.DEV_NULL_LOGGING; import static org.neo4j.driver.internal.util.Futures.completedWithNull; @@ -267,17 +268,19 @@ public static NetworkSession newSession( ConnectionProvider connectionProvider ) public static NetworkSession newSession( ConnectionProvider connectionProvider, AccessMode mode, RetryLogic retryLogic, InternalBookmark bookmark ) { - return new NetworkSession( connectionProvider, retryLogic, defaultDatabase(), mode, new DefaultBookmarkHolder( bookmark ), DEV_NULL_LOGGING ); + return new NetworkSession( connectionProvider, retryLogic, defaultDatabase(), mode, new DefaultBookmarkHolder( bookmark ), UNLIMITED_FETCH_SIZE, + DEV_NULL_LOGGING ); } - public static void verifyRun( Connection connection, String query ) + public static void verifyRunRx( Connection connection, String query ) { verify( connection ).writeAndFlush( argThat( runWithMetaMessageWithStatementMatcher( query ) ), any() ); } public static void verifyRunAndPull( Connection connection, String query ) { - verify( connection ).writeAndFlush( argThat( runWithMetaMessageWithStatementMatcher( query ) ), any(), any( PullMessage.class ), any() ); + verify( connection ).write( argThat( runWithMetaMessageWithStatementMatcher( query ) ), any() ); + verify( connection ).writeAndFlush( any( PullMessage.class ), any() ); } public static void verifyCommitTx( Connection connection, VerificationMode mode ) @@ -323,10 +326,16 @@ public static void setupFailingRun( Connection connection, Throwable error ) { ResponseHandler runHandler = invocation.getArgument( 1 ); runHandler.onFailure( error ); - ResponseHandler pullHandler = invocation.getArgument( 3 ); + return null; + } ).when( connection ).writeAndFlush( any( RunWithMetadataMessage.class ), any() ); + + doAnswer( invocation -> + { + ResponseHandler pullHandler = invocation.getArgument( 1 ); pullHandler.onFailure( error ); return null; - } ).when( connection ).writeAndFlush( any( RunWithMetadataMessage.class ), any(), any( PullMessage.class ), any() ); + } ).when( connection ).writeAndFlush( any( PullMessage.class ), any() ); + } public static void setupFailingBegin( Connection connection, Throwable error ) @@ -402,13 +411,18 @@ public static void setupSuccessfulRunAndPull( Connection connection ) { ResponseHandler runHandler = invocation.getArgument( 1 ); runHandler.onSuccess( emptyMap() ); - ResponseHandler pullHandler = invocation.getArgument( 3 ); + return null; + } ).when( connection ).write( any( RunWithMetadataMessage.class ), any() ); + + doAnswer( invocation -> + { + ResponseHandler pullHandler = invocation.getArgument( 1 ); pullHandler.onSuccess( emptyMap() ); return null; - } ).when( connection ).writeAndFlush( any( RunWithMetadataMessage.class ), any(), any( PullMessage.class ), any() ); + } ).when( connection ).writeAndFlush( any( PullMessage.class ), any() ); } - public static void setupSuccessfulRun( Connection connection ) + public static void setupSuccessfulRunRx( Connection connection ) { doAnswer( invocation -> { @@ -424,10 +438,15 @@ public static void setupSuccessfulRunAndPull( Connection connection, String quer { ResponseHandler runHandler = invocation.getArgument( 1 ); runHandler.onSuccess( emptyMap() ); - ResponseHandler pullHandler = invocation.getArgument( 3 ); + return null; + } ).when( connection ).write( argThat( runWithMetaMessageWithStatementMatcher( query ) ), any() ); + + doAnswer( invocation -> + { + ResponseHandler pullHandler = invocation.getArgument( 1 ); pullHandler.onSuccess( emptyMap() ); return null; - } ).when( connection ).writeAndFlush( argThat( runWithMetaMessageWithStatementMatcher( query ) ), any(), any( PullMessage.class ), any() ); + } ).when( connection ).writeAndFlush( any( PullMessage.class ), any() ); } public static Connection connectionMock() diff --git a/driver/src/test/resources/read_server_v4_read.script b/driver/src/test/resources/read_server_v4_read.script index df53ef4d5f..25396130e9 100644 --- a/driver/src/test/resources/read_server_v4_read.script +++ b/driver/src/test/resources/read_server_v4_read.script @@ -4,7 +4,7 @@ !: AUTO GOODBYE C: RUN "MATCH (n) RETURN n.name" {} { "mode": "r", "db": "mydatabase" } - PULL { "n": -1 } + PULL { "n": 1000 } S: SUCCESS {"fields": ["n.name"]} RECORD ["Bob"] RECORD ["Alice"] diff --git a/driver/src/test/resources/read_server_v4_read_tx.script b/driver/src/test/resources/read_server_v4_read_tx.script index 10224bd1f9..d7d0a5bb9e 100644 --- a/driver/src/test/resources/read_server_v4_read_tx.script +++ b/driver/src/test/resources/read_server_v4_read_tx.script @@ -6,7 +6,7 @@ C: BEGIN { "mode": "r", "db": "mydatabase" } S: SUCCESS {} C: RUN "MATCH (n) RETURN n.name" {} {} - PULL { "n": -1 } + PULL { "n": 1000 } S: SUCCESS {"fields": ["n.name"]} RECORD ["Bob"] RECORD ["Alice"] diff --git a/driver/src/test/resources/read_server_v4_read_with_bookmark.script b/driver/src/test/resources/read_server_v4_read_with_bookmark.script index 1e793b90d0..837b12b59e 100644 --- a/driver/src/test/resources/read_server_v4_read_with_bookmark.script +++ b/driver/src/test/resources/read_server_v4_read_with_bookmark.script @@ -4,7 +4,7 @@ !: AUTO GOODBYE C: RUN "MATCH (n) RETURN n.name" {} { "mode": "r", "db": "foo", "bookmarks": ["sys:1234", "foo:5678"] } - PULL { "n": -1 } + PULL { "n": 1000 } S: SUCCESS {"fields": ["n.name"]} RECORD ["Bob"] RECORD ["Alice"] diff --git a/driver/src/test/resources/streaming_records_v4.script b/driver/src/test/resources/streaming_records_v4.script new file mode 100644 index 0000000000..2a993d64c4 --- /dev/null +++ b/driver/src/test/resources/streaming_records_v4.script @@ -0,0 +1,14 @@ +!: BOLT 4 +!: AUTO RESET +!: AUTO HELLO +!: AUTO GOODBYE + +C: RUN "MATCH (n) RETURN n.name" {} {} + PULL { "n": 2 } +S: SUCCESS {"fields": ["n.name"]} + RECORD ["Bob"] + RECORD ["Alice"] + SUCCESS {"has_more": true} +C: PULL { "n": -1 } +S: RECORD ["Tina"] + SUCCESS {} diff --git a/driver/src/test/resources/streaming_records_v4_all.script b/driver/src/test/resources/streaming_records_v4_all.script new file mode 100644 index 0000000000..f9e0386b5a --- /dev/null +++ b/driver/src/test/resources/streaming_records_v4_all.script @@ -0,0 +1,12 @@ +!: BOLT 4 +!: AUTO RESET +!: AUTO HELLO +!: AUTO GOODBYE + +C: RUN "MATCH (n) RETURN n.name" {} {} + PULL { "n": -1 } +S: SUCCESS {"fields": ["n.name"]} + RECORD ["Bob"] + RECORD ["Alice"] + RECORD ["Tina"] + SUCCESS {} diff --git a/driver/src/test/resources/streaming_records_v4_rx.script b/driver/src/test/resources/streaming_records_v4_rx.script new file mode 100644 index 0000000000..74543b6f86 --- /dev/null +++ b/driver/src/test/resources/streaming_records_v4_rx.script @@ -0,0 +1,14 @@ +!: BOLT 4 +!: AUTO RESET +!: AUTO HELLO +!: AUTO GOODBYE + +C: RUN "MATCH (n) RETURN n.name" {} {} +S: SUCCESS {"fields": ["n.name"]} +C: PULL { "n": 2 } +S: RECORD ["Bob"] + RECORD ["Alice"] + SUCCESS {"has_more": true} +C: PULL { "n": 2 } +S: RECORD ["Tina"] + SUCCESS {}