diff --git a/driver/src/main/java/org/neo4j/driver/Session.java b/driver/src/main/java/org/neo4j/driver/Session.java index d65fbf3c23..53193aed26 100644 --- a/driver/src/main/java/org/neo4j/driver/Session.java +++ b/driver/src/main/java/org/neo4j/driver/Session.java @@ -21,6 +21,7 @@ import java.util.Map; import org.neo4j.driver.async.AsyncSession; +import org.neo4j.driver.internal.Bookmark; import org.neo4j.driver.util.Resource; /** @@ -212,7 +213,7 @@ public interface Session extends Resource, StatementRunner * * @return a reference to a previous transaction */ - String lastBookmark(); + Bookmark lastBookmark(); /** * Reset the current session. This sends an immediate RESET signal to the server which both interrupts diff --git a/driver/src/main/java/org/neo4j/driver/SessionConfig.java b/driver/src/main/java/org/neo4j/driver/SessionConfig.java index d1faab6a33..b105c2848c 100644 --- a/driver/src/main/java/org/neo4j/driver/SessionConfig.java +++ b/driver/src/main/java/org/neo4j/driver/SessionConfig.java @@ -19,11 +19,11 @@ package org.neo4j.driver; import java.util.Arrays; -import java.util.List; import java.util.Objects; import java.util.Optional; import org.neo4j.driver.async.AsyncSession; +import org.neo4j.driver.internal.Bookmark; import org.neo4j.driver.reactive.RxSession; import static java.util.Objects.requireNonNull; @@ -36,7 +36,7 @@ public class SessionConfig { private static final SessionConfig EMPTY = builder().build(); - private final List bookmarks; + private final Iterable bookmarks; private final AccessMode defaultAccessMode; private final String database; @@ -85,7 +85,7 @@ public static SessionConfig forDatabase( String database ) * * @return the initial bookmarks. */ - public List bookmarks() + public Iterable bookmarks() { return bookmarks; } @@ -143,7 +143,7 @@ public String toString() */ public static class Builder { - private List bookmarks = null; + private Iterable bookmarks = null; private AccessMode defaultAccessMode = AccessMode.WRITE; private String database = null; @@ -164,11 +164,10 @@ private Builder() * are permitted, and indicate that the bookmarks do not exist or are unknown. * @return this builder. */ - public Builder withBookmarks( String... bookmarks ) + public Builder withBookmarks( Bookmark... bookmarks ) { if ( bookmarks == null ) { - // TODO bookmarks should not be null this.bookmarks = null; } else @@ -189,7 +188,7 @@ public Builder withBookmarks( String... bookmarks ) * are permitted, and indicate that the bookmarks do not exist or are unknown. * @return this builder */ - public Builder withBookmarks( List bookmarks ) + public Builder withBookmarks( Iterable bookmarks ) { this.bookmarks = bookmarks; return this; @@ -229,7 +228,11 @@ public Builder withDatabase( String database ) // Disallow users to use bolt internal value directly. To users, this is totally an illegal database name. throw new IllegalArgumentException( String.format( "Illegal database name '%s'.", database ) ); } - this.database = database; + // The database name is normalized to lowercase on the server side. + // The client in theory shall not perform any normalization at all. + // However as this name is also used in routing table registry as the map's key to find the routing table for the given database, + // to avoid multiple routing tables for the same database, we perform a client side normalization. + this.database = database.toLowerCase(); return this; } diff --git a/driver/src/main/java/org/neo4j/driver/async/AsyncSession.java b/driver/src/main/java/org/neo4j/driver/async/AsyncSession.java index 7c70e0e736..36e5f52a65 100644 --- a/driver/src/main/java/org/neo4j/driver/async/AsyncSession.java +++ b/driver/src/main/java/org/neo4j/driver/async/AsyncSession.java @@ -29,6 +29,7 @@ import org.neo4j.driver.Transaction; import org.neo4j.driver.TransactionConfig; import org.neo4j.driver.Values; +import org.neo4j.driver.internal.Bookmark; /** * Provides a context of work for database interactions. @@ -301,7 +302,7 @@ public interface AsyncSession extends AsyncStatementRunner * * @return a reference to a previous transaction */ - String lastBookmark(); + Bookmark lastBookmark(); /** * Signal that you are done using this session. In the default driver usage, closing and accessing sessions is diff --git a/driver/src/main/java/org/neo4j/driver/internal/Bookmark.java b/driver/src/main/java/org/neo4j/driver/internal/Bookmark.java new file mode 100644 index 0000000000..4649215a95 --- /dev/null +++ b/driver/src/main/java/org/neo4j/driver/internal/Bookmark.java @@ -0,0 +1,36 @@ +/* + * 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; + +import java.io.Serializable; + +/** + * Causal chaining is carried out by passing bookmarks between transactions. + * + * When starting a session with initial bookmarks, the first transaction will be ensured to run at least after + * the database is as up-to-date as the latest transaction referenced by the supplied bookmarks. + * + * Within a session, bookmark propagation is carried out automatically. + * Thus all transactions in a session including explicit and implicit transactions are ensured to be carried out one after another. + * + * To opt out of this mechanism for unrelated units of work, applications can use multiple sessions. + */ +public interface Bookmark extends Serializable +{ +} diff --git a/driver/src/main/java/org/neo4j/driver/internal/BookmarksHolder.java b/driver/src/main/java/org/neo4j/driver/internal/BookmarkHolder.java similarity index 65% rename from driver/src/main/java/org/neo4j/driver/internal/BookmarksHolder.java rename to driver/src/main/java/org/neo4j/driver/internal/BookmarkHolder.java index 1bf492b6c4..bc8f827118 100644 --- a/driver/src/main/java/org/neo4j/driver/internal/BookmarksHolder.java +++ b/driver/src/main/java/org/neo4j/driver/internal/BookmarkHolder.java @@ -18,31 +18,23 @@ */ package org.neo4j.driver.internal; -public interface BookmarksHolder +public interface BookmarkHolder { - Bookmarks getBookmarks(); + InternalBookmark getBookmark(); - void setBookmarks( Bookmarks bookmarks ); + void setBookmark( InternalBookmark bookmark ); - String lastBookmark(); - - BookmarksHolder NO_OP = new BookmarksHolder() + BookmarkHolder NO_OP = new BookmarkHolder() { @Override - public Bookmarks getBookmarks() - { - return Bookmarks.empty(); - } - - @Override - public void setBookmarks( Bookmarks bookmarks ) + public InternalBookmark getBookmark() { + return InternalBookmark.empty(); } @Override - public String lastBookmark() + public void setBookmark( InternalBookmark bookmark ) { - return null; } }; } diff --git a/driver/src/main/java/org/neo4j/driver/internal/Bookmarks.java b/driver/src/main/java/org/neo4j/driver/internal/Bookmarks.java deleted file mode 100644 index de07adb32b..0000000000 --- a/driver/src/main/java/org/neo4j/driver/internal/Bookmarks.java +++ /dev/null @@ -1,182 +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; - -import java.util.Collections; -import java.util.Iterator; -import java.util.Map; -import java.util.Objects; - -import org.neo4j.driver.Value; - -import static java.util.Collections.emptyMap; -import static java.util.Collections.singleton; -import static org.neo4j.driver.internal.util.Iterables.newHashMapWithSize; -import static org.neo4j.driver.Values.value; - -public final class Bookmarks -{ - private static final String BOOKMARK_KEY = "bookmark"; - private static final String BOOKMARKS_KEY = "bookmarks"; - private static final String BOOKMARK_PREFIX = "neo4j:bookmark:v1:tx"; - - private static final long UNKNOWN_BOOKMARK_VALUE = -1; - - private static final Bookmarks EMPTY = new Bookmarks( Collections.emptySet() ); - - private final Iterable values; - private final String maxValue; - - private Bookmarks( Iterable values ) - { - this.values = values; - this.maxValue = maxBookmark( values ); - } - - public static Bookmarks empty() - { - return EMPTY; - } - - public static Bookmarks from( String value ) - { - if ( value == null ) - { - return empty(); - } - return from( singleton( value ) ); - } - - public static Bookmarks from( Iterable values ) - { - if ( values == null ) - { - return empty(); - } - return new Bookmarks( values ); - } - - public boolean isEmpty() - { - return maxValue == null; - } - - public String maxBookmarkAsString() - { - return maxValue; - } - - public Iterable values() - { - return values; - } - - public Map asBeginTransactionParameters() - { - if ( isEmpty() ) - { - return emptyMap(); - } - - // Driver sends {bookmark: "max", bookmarks: ["one", "two", "max"]} instead of simple - // {bookmarks: ["one", "two", "max"]} for backwards compatibility reasons. Old servers can only accept single - // bookmark that is why driver has to parse and compare given list of bookmarks. This functionality will - // eventually be removed. - Map parameters = newHashMapWithSize( 2 ); - parameters.put( BOOKMARK_KEY, value( maxValue ) ); - parameters.put( BOOKMARKS_KEY, value( values ) ); - return parameters; - } - - @Override - public boolean equals( Object o ) - { - if ( this == o ) - { - return true; - } - if ( o == null || getClass() != o.getClass() ) - { - return false; - } - Bookmarks bookmarks = (Bookmarks) o; - return Objects.equals( values, bookmarks.values ) && - Objects.equals( maxValue, bookmarks.maxValue ); - } - - @Override - public int hashCode() - { - return Objects.hash( values, maxValue ); - } - - @Override - public String toString() - { - return "Bookmarks{values=" + values + "}"; - } - - private static String maxBookmark( Iterable bookmarks ) - { - if ( bookmarks == null ) - { - return null; - } - - Iterator iterator = bookmarks.iterator(); - - if ( !iterator.hasNext() ) - { - return null; - } - - String maxBookmark = iterator.next(); - long maxValue = bookmarkValue( maxBookmark ); - - while ( iterator.hasNext() ) - { - String bookmark = iterator.next(); - long value = bookmarkValue( bookmark ); - - if ( value > maxValue ) - { - maxBookmark = bookmark; - maxValue = value; - } - } - - return maxBookmark; - } - - private static long bookmarkValue( String value ) - { - if ( value != null && value.startsWith( BOOKMARK_PREFIX ) ) - { - try - { - return Long.parseLong( value.substring( BOOKMARK_PREFIX.length() ) ); - } - catch ( NumberFormatException e ) - { - return UNKNOWN_BOOKMARK_VALUE; - } - } - return UNKNOWN_BOOKMARK_VALUE; - } -} diff --git a/driver/src/main/java/org/neo4j/driver/internal/DefaultBookmarksHolder.java b/driver/src/main/java/org/neo4j/driver/internal/DefaultBookmarkHolder.java similarity index 57% rename from driver/src/main/java/org/neo4j/driver/internal/DefaultBookmarksHolder.java rename to driver/src/main/java/org/neo4j/driver/internal/DefaultBookmarkHolder.java index 04c0f81023..2563729e1c 100644 --- a/driver/src/main/java/org/neo4j/driver/internal/DefaultBookmarksHolder.java +++ b/driver/src/main/java/org/neo4j/driver/internal/DefaultBookmarkHolder.java @@ -21,38 +21,32 @@ /** * @since 2.0 */ -public class DefaultBookmarksHolder implements BookmarksHolder +public class DefaultBookmarkHolder implements BookmarkHolder { - private volatile Bookmarks bookmarks; + private volatile InternalBookmark bookmark; - public DefaultBookmarksHolder() + public DefaultBookmarkHolder() { - this( Bookmarks.empty() ); + this( InternalBookmark.empty() ); } - public DefaultBookmarksHolder( Bookmarks bookmarks ) + public DefaultBookmarkHolder( InternalBookmark bookmark ) { - this.bookmarks = bookmarks; + this.bookmark = bookmark; } @Override - public Bookmarks getBookmarks() + public InternalBookmark getBookmark() { - return bookmarks; + return bookmark; } @Override - public void setBookmarks( Bookmarks bookmarks ) + public void setBookmark( InternalBookmark bookmark ) { - if ( bookmarks != null && !bookmarks.isEmpty() ) + if ( bookmark != null && !bookmark.isEmpty() ) { - this.bookmarks = bookmarks; + this.bookmark = bookmark; } } - - @Override - public String lastBookmark() - { - return bookmarks == null ? null : bookmarks.maxBookmarkAsString(); - } } diff --git a/driver/src/main/java/org/neo4j/driver/internal/DirectConnectionProvider.java b/driver/src/main/java/org/neo4j/driver/internal/DirectConnectionProvider.java index 6bfbbbf6d0..bf87f68ed1 100644 --- a/driver/src/main/java/org/neo4j/driver/internal/DirectConnectionProvider.java +++ b/driver/src/main/java/org/neo4j/driver/internal/DirectConnectionProvider.java @@ -20,14 +20,13 @@ import java.util.concurrent.CompletionStage; -import org.neo4j.driver.AccessMode; +import org.neo4j.driver.internal.async.ConnectionContext; import org.neo4j.driver.internal.async.connection.DirectConnection; import org.neo4j.driver.internal.spi.Connection; import org.neo4j.driver.internal.spi.ConnectionPool; import org.neo4j.driver.internal.spi.ConnectionProvider; -import static org.neo4j.driver.AccessMode.READ; -import static org.neo4j.driver.internal.messaging.request.MultiDatabaseUtil.ABSENT_DB_NAME; +import static org.neo4j.driver.internal.async.ImmutableConnectionContext.simple; /** * Simple {@link ConnectionProvider connection provider} that obtains connections form the given pool only for @@ -45,17 +44,17 @@ public class DirectConnectionProvider implements ConnectionProvider } @Override - public CompletionStage acquireConnection( String databaseName, AccessMode mode ) + public CompletionStage acquireConnection( ConnectionContext context ) { - return connectionPool.acquire( address ).thenApply( connection -> new DirectConnection( connection, databaseName, mode ) ); + return connectionPool.acquire( address ).thenApply( connection -> new DirectConnection( connection, context.databaseName(), context.mode() ) ); } @Override public CompletionStage verifyConnectivity() { // We verify the connection by establishing a connection with the remote server specified by the address. - // Database name will be ignored as no query is run in this connection and the connection is released immediately. - return acquireConnection( ABSENT_DB_NAME, READ ).thenCompose( Connection::release ); + // Connection context will be ignored as no query is run in this connection and the connection is released immediately. + return acquireConnection( simple() ).thenCompose( Connection::release ); } @Override diff --git a/driver/src/main/java/org/neo4j/driver/internal/InternalBookmark.java b/driver/src/main/java/org/neo4j/driver/internal/InternalBookmark.java new file mode 100644 index 0000000000..4150c8151b --- /dev/null +++ b/driver/src/main/java/org/neo4j/driver/internal/InternalBookmark.java @@ -0,0 +1,161 @@ +/* + * 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; + +import java.io.Serializable; +import java.util.Collection; +import java.util.Collections; +import java.util.HashSet; +import java.util.Objects; +import java.util.Set; + +import static java.util.Objects.requireNonNull; + +public final class InternalBookmark implements Bookmark +{ + private static final InternalBookmark EMPTY = new InternalBookmark( Collections.emptySet() ); + + private final Collection values; + + private InternalBookmark( Collection values ) + { + requireNonNull( values ); + if ( !(values instanceof Serializable) ) + { + // The Collection interface does not enforce Serializable, but all built-in Collection implementations actually are Serializable. + // This check ensures that we always provide values using these java built-in Collection objects. + throw new IllegalArgumentException( "The bookmark value should only be of Java built-in types such as ArrayList, HashSet which are serializable." ); + } + this.values = values; + } + + public static InternalBookmark empty() + { + return EMPTY; + } + + public static InternalBookmark from( Iterable bookmarks ) + { + if ( bookmarks == null ) + { + return empty(); + } + + if ( bookmarks instanceof Collection ) + { + int size = ((Collection) bookmarks).size(); + if ( size == 0 ) + { + return empty(); + } + else if ( size == 1 ) + { + return from( bookmarks.iterator().next() ); + } + } + + Set newValues = new HashSet<>(); + for ( Bookmark value : bookmarks ) + { + if ( value == null ) + { + continue; // skip any null bookmark value + } + assertInternalBookmark( value ); + newValues.addAll( ((InternalBookmark) value).values ); + } + return new InternalBookmark( newValues ); + } + + private static InternalBookmark from( Bookmark bookmark ) + { + if ( bookmark == null ) + { + return empty(); + } + assertInternalBookmark( bookmark ); + return (InternalBookmark) bookmark; // we directly return the same bookmark back + } + + private static void assertInternalBookmark( Bookmark bookmark ) + { + if ( !(bookmark instanceof InternalBookmark) ) + { + throw new IllegalArgumentException( String.format( "Received bookmark '%s' is not generated by driver sessions.", bookmark ) ); + } + } + + public static InternalBookmark parse( String value ) + { + if ( value == null ) + { + return empty(); + } + return parse( Collections.singletonList( value ) ); + } + + /** + * Used for test only + */ + public static InternalBookmark parse( Collection values ) + { + if ( values == null ) + { + return empty(); + } + return new InternalBookmark( values ); + } + + public boolean isEmpty() + { + return values.isEmpty(); + } + + public Iterable values() + { + return values; + } + + @Override + public boolean equals( Object o ) + { + if ( this == o ) + { + return true; + } + if ( o == null || getClass() != o.getClass() ) + { + return false; + } + InternalBookmark bookmark = (InternalBookmark) o; + return Objects.equals( values, bookmark.values ); + } + + @Override + public int hashCode() + { + return Objects.hash( values ); + } + + @Override + public String toString() + { + return "Bookmark{values=" + values + "}"; + } +} diff --git a/driver/src/main/java/org/neo4j/driver/internal/InternalSession.java b/driver/src/main/java/org/neo4j/driver/internal/InternalSession.java index 5c937c8fa4..e92766f1d8 100644 --- a/driver/src/main/java/org/neo4j/driver/internal/InternalSession.java +++ b/driver/src/main/java/org/neo4j/driver/internal/InternalSession.java @@ -124,7 +124,7 @@ public T writeTransaction( TransactionWork work, TransactionConfig config } @Override - public String lastBookmark() + public Bookmark lastBookmark() { return session.lastBookmark(); } diff --git a/driver/src/main/java/org/neo4j/driver/internal/ReadOnlyBookmarkHolder.java b/driver/src/main/java/org/neo4j/driver/internal/ReadOnlyBookmarkHolder.java new file mode 100644 index 0000000000..10be0ad662 --- /dev/null +++ b/driver/src/main/java/org/neo4j/driver/internal/ReadOnlyBookmarkHolder.java @@ -0,0 +1,44 @@ +/* + * 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; + +/** + * @since 2.0 + */ +public class ReadOnlyBookmarkHolder implements BookmarkHolder +{ + private final InternalBookmark bookmark; + + public ReadOnlyBookmarkHolder( InternalBookmark bookmark ) + { + this.bookmark = bookmark; + } + + @Override + public InternalBookmark getBookmark() + { + return bookmark; + } + + @Override + public void setBookmark( InternalBookmark bookmark ) + { + // NO_OP + } +} 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 7af35da2c3..edfd3eac27 100644 --- a/driver/src/main/java/org/neo4j/driver/internal/SessionFactoryImpl.java +++ b/driver/src/main/java/org/neo4j/driver/internal/SessionFactoryImpl.java @@ -47,10 +47,11 @@ public class SessionFactoryImpl implements SessionFactory } @Override - public NetworkSession newInstance( SessionConfig parameters ) + public NetworkSession newInstance( SessionConfig sessionConfig ) { - BookmarksHolder bookmarksHolder = new DefaultBookmarksHolder( Bookmarks.from( parameters.bookmarks() ) ); - return createSession( connectionProvider, retryLogic, parameters.database().orElse( ABSENT_DB_NAME ), parameters.defaultAccessMode(), bookmarksHolder, logging ); + BookmarkHolder bookmarkHolder = new DefaultBookmarkHolder( InternalBookmark.from( sessionConfig.bookmarks() ) ); + return createSession( connectionProvider, retryLogic, sessionConfig.database().orElse( ABSENT_DB_NAME ), + sessionConfig.defaultAccessMode(), bookmarkHolder, logging ); } @Override @@ -78,10 +79,10 @@ public ConnectionProvider getConnectionProvider() } private NetworkSession createSession( ConnectionProvider connectionProvider, RetryLogic retryLogic, String databaseName, AccessMode mode, - BookmarksHolder bookmarksHolder, Logging logging ) + BookmarkHolder bookmarkHolder, Logging logging ) { return leakedSessionsLoggingEnabled - ? new LeakLoggingNetworkSession( connectionProvider, retryLogic, databaseName, mode, bookmarksHolder, logging ) - : new NetworkSession( connectionProvider, retryLogic, databaseName, mode, bookmarksHolder, logging ); + ? new LeakLoggingNetworkSession( connectionProvider, retryLogic, databaseName, mode, bookmarkHolder, logging ) + : new NetworkSession( connectionProvider, retryLogic, databaseName, mode, bookmarkHolder, logging ); } } diff --git a/driver/src/main/java/org/neo4j/driver/internal/async/ConnectionContext.java b/driver/src/main/java/org/neo4j/driver/internal/async/ConnectionContext.java new file mode 100644 index 0000000000..d93a714d6d --- /dev/null +++ b/driver/src/main/java/org/neo4j/driver/internal/async/ConnectionContext.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.async; + +import org.neo4j.driver.AccessMode; +import org.neo4j.driver.internal.InternalBookmark; + +public interface ConnectionContext +{ + String databaseName(); + + AccessMode mode(); + + InternalBookmark rediscoveryBookmark(); +} 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 fc1e848240..5c4c272810 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 @@ -27,8 +27,8 @@ import org.neo4j.driver.TransactionConfig; import org.neo4j.driver.async.StatementResultCursor; import org.neo4j.driver.exceptions.ClientException; -import org.neo4j.driver.internal.Bookmarks; -import org.neo4j.driver.internal.BookmarksHolder; +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.RxStatementResultCursor; import org.neo4j.driver.internal.messaging.BoltProtocol; @@ -66,22 +66,22 @@ private enum State private final Connection connection; private final BoltProtocol protocol; - private final BookmarksHolder bookmarksHolder; + private final BookmarkHolder bookmarkHolder; private final ResultCursorsHolder resultCursors; private volatile State state = State.ACTIVE; - public ExplicitTransaction( Connection connection, BookmarksHolder bookmarksHolder ) + public ExplicitTransaction( Connection connection, BookmarkHolder bookmarkHolder ) { this.connection = connection; this.protocol = connection.protocol(); - this.bookmarksHolder = bookmarksHolder; + this.bookmarkHolder = bookmarkHolder; this.resultCursors = new ResultCursorsHolder(); } - public CompletionStage beginAsync( Bookmarks initialBookmarks, TransactionConfig config ) + public CompletionStage beginAsync( InternalBookmark initialBookmark, TransactionConfig config ) { - return protocol.beginTransaction( connection, initialBookmarks, config ) + return protocol.beginTransaction( connection, initialBookmark, config ) .handle( ( ignore, beginError ) -> { if ( beginError != null ) @@ -224,7 +224,7 @@ private CompletionStage doCommitAsync() return failedFuture( new ClientException( "Transaction can't be committed. " + "It has been rolled back either because of an error or explicit termination" ) ); } - return protocol.commitTransaction( connection ).thenAccept( bookmarksHolder::setBookmarks ); + return protocol.commitTransaction( connection ).thenAccept( bookmarkHolder::setBookmark ); } private CompletionStage doRollbackAsync() diff --git a/driver/src/main/java/org/neo4j/driver/internal/async/ImmutableConnectionContext.java b/driver/src/main/java/org/neo4j/driver/internal/async/ImmutableConnectionContext.java new file mode 100644 index 0000000000..011bc3958b --- /dev/null +++ b/driver/src/main/java/org/neo4j/driver/internal/async/ImmutableConnectionContext.java @@ -0,0 +1,73 @@ +/* + * 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.async; + +import org.neo4j.driver.AccessMode; +import org.neo4j.driver.internal.InternalBookmark; +import org.neo4j.driver.internal.spi.Connection; + +import static org.neo4j.driver.internal.InternalBookmark.empty; +import static org.neo4j.driver.internal.messaging.request.MultiDatabaseUtil.ABSENT_DB_NAME; + +/** + * A {@link Connection} shall fulfil this {@link ImmutableConnectionContext} when acquired from a connection provider. + */ +public class ImmutableConnectionContext implements ConnectionContext +{ + private static final ConnectionContext SIMPLE = new ImmutableConnectionContext( ABSENT_DB_NAME, empty(), AccessMode.READ ); + + private final String databaseName; + private final AccessMode mode; + private final InternalBookmark rediscoveryBookmark; + + public ImmutableConnectionContext( String databaseName, InternalBookmark bookmark, AccessMode mode ) + { + this.databaseName = databaseName; + this.rediscoveryBookmark = bookmark; + this.mode = mode; + } + + @Override + public String databaseName() + { + return databaseName; + } + + @Override + public AccessMode mode() + { + return mode; + } + + @Override + public InternalBookmark rediscoveryBookmark() + { + return rediscoveryBookmark; + } + + /** + * A simple context is used to test connectivity with a remote server/cluster. + * As long as there is a read only service, the connection shall be established successfully. + * This context should be applicable for both bolt v4 and bolt v3 routing table rediscovery. + */ + public static ConnectionContext simple() + { + return SIMPLE; + } +} diff --git a/driver/src/main/java/org/neo4j/driver/internal/async/InternalAsyncSession.java b/driver/src/main/java/org/neo4j/driver/internal/async/InternalAsyncSession.java index 7dc5837fba..220070f4ff 100644 --- a/driver/src/main/java/org/neo4j/driver/internal/async/InternalAsyncSession.java +++ b/driver/src/main/java/org/neo4j/driver/internal/async/InternalAsyncSession.java @@ -29,6 +29,7 @@ import org.neo4j.driver.async.AsyncTransaction; import org.neo4j.driver.async.AsyncTransactionWork; import org.neo4j.driver.async.StatementResultCursor; +import org.neo4j.driver.internal.Bookmark; import org.neo4j.driver.internal.util.Futures; import static java.util.Collections.emptyMap; @@ -111,7 +112,7 @@ public CompletionStage writeTransactionAsync( AsyncTransactionWork transactionStage = completedWithNull(); private volatile CompletionStage connectionStage = completedWithNull(); private volatile CompletionStage resultCursorStage = completedWithNull(); @@ -61,14 +63,14 @@ public class NetworkSession private final AtomicBoolean open = new AtomicBoolean( true ); public NetworkSession( ConnectionProvider connectionProvider, RetryLogic retryLogic, String databaseName, AccessMode mode, - BookmarksHolder bookmarksHolder, Logging logging ) + BookmarkHolder bookmarkHolder, Logging logging ) { this.connectionProvider = connectionProvider; this.mode = mode; this.retryLogic = retryLogic; this.logger = new PrefixedLogger( "[" + hashCode() + "]", logging.getLog( LOG_NAME ) ); - this.bookmarksHolder = bookmarksHolder; - this.databaseName = databaseName; + this.bookmarkHolder = bookmarkHolder; + this.connectionContext = new NetworkSessionConnectionContext( databaseName, bookmarkHolder.getBookmark() ); } public CompletionStage runAsync( Statement statement, TransactionConfig config, boolean waitForRunResponse ) @@ -100,11 +102,11 @@ public CompletionStage beginTransactionAsync( AccessMode mo // create a chain that acquires connection and starts a transaction CompletionStage newTransactionStage = ensureNoOpenTxBeforeStartingTx() - .thenCompose( ignore -> acquireConnection( databaseName, mode ) ) + .thenCompose( ignore -> acquireConnection( mode ) ) .thenCompose( connection -> { - ExplicitTransaction tx = new ExplicitTransaction( connection, bookmarksHolder ); - return tx.beginAsync( bookmarksHolder.getBookmarks(), config ); + ExplicitTransaction tx = new ExplicitTransaction( connection, bookmarkHolder ); + return tx.beginAsync( bookmarkHolder.getBookmark(), config ); } ); // update the reference to the only known transaction @@ -153,9 +155,9 @@ public RetryLogic retryLogic() return retryLogic; } - public String lastBookmark() + public Bookmark lastBookmark() { - return bookmarksHolder.lastBookmark(); + return bookmarkHolder.getBookmark(); } public CompletionStage releaseConnectionAsync() @@ -223,12 +225,12 @@ private CompletionStage buildResultCursorFactory( ensureSessionIsOpen(); return ensureNoOpenTxBeforeRunningQuery() - .thenCompose( ignore -> acquireConnection( databaseName, mode ) ) + .thenCompose( ignore -> acquireConnection( mode ) ) .thenCompose( connection -> { try { StatementResultCursorFactory factory = connection.protocol() - .runInAutoCommitTransaction( connection, statement, bookmarksHolder, config, waitForRunResponse ); + .runInAutoCommitTransaction( connection, statement, bookmarkHolder, config, waitForRunResponse ); return completedFuture( factory ); } catch ( Throwable e ) @@ -238,7 +240,7 @@ private CompletionStage buildResultCursorFactory( } ); } - private CompletionStage acquireConnection( String databaseName, AccessMode mode ) + private CompletionStage acquireConnection( AccessMode mode ) { CompletionStage currentConnectionStage = connectionStage; @@ -274,7 +276,7 @@ private CompletionStage acquireConnection( String databaseName, Acce // there somehow is an existing open connection, this should not happen, just a precondition throw new IllegalStateException( "Existing open connection detected" ); } - return connectionProvider.acquireConnection( databaseName, mode ); + return connectionProvider.acquireConnection( connectionContext.contextWithMode( mode ) ); } ); connectionStage = newConnectionStage.exceptionally( error -> null ); @@ -338,4 +340,49 @@ private void ensureSessionIsOpen() "No more interaction with this session are allowed as the current session is already closed. " ); } } + + /** + * A {@link Connection} shall fulfil this {@link ImmutableConnectionContext} when acquired from a connection provider. + */ + private class NetworkSessionConnectionContext implements ConnectionContext + { + private final String databaseName; + private AccessMode mode; + + // This bookmark is only used for rediscovery. + // It has to be the initial bookmark given at the creation of the session. + // As only that bookmark could carry extra system bookmarks + private final InternalBookmark rediscoveryBookmark; + + private NetworkSessionConnectionContext( String databaseName, InternalBookmark bookmark ) + { + this.databaseName = databaseName; + this.rediscoveryBookmark = bookmark; + } + + private ConnectionContext contextWithMode( AccessMode mode ) + { + this.mode = mode; + return this; + } + + @Override + public String databaseName() + { + return databaseName; + } + + @Override + public AccessMode mode() + { + return mode; + } + + @Override + public InternalBookmark rediscoveryBookmark() + { + return rediscoveryBookmark; + } + } + } diff --git a/driver/src/main/java/org/neo4j/driver/internal/cluster/ClusterCompositionProvider.java b/driver/src/main/java/org/neo4j/driver/internal/cluster/ClusterCompositionProvider.java index ff58366e75..9282345461 100644 --- a/driver/src/main/java/org/neo4j/driver/internal/cluster/ClusterCompositionProvider.java +++ b/driver/src/main/java/org/neo4j/driver/internal/cluster/ClusterCompositionProvider.java @@ -20,9 +20,10 @@ import java.util.concurrent.CompletionStage; +import org.neo4j.driver.internal.InternalBookmark; import org.neo4j.driver.internal.spi.Connection; public interface ClusterCompositionProvider { - CompletionStage getClusterComposition( CompletionStage connectionStage, String databaseName ); + CompletionStage getClusterComposition( Connection connection, String databaseName, InternalBookmark bookmark ); } diff --git a/driver/src/main/java/org/neo4j/driver/internal/cluster/MultiDatabasesRoutingProcedureRunner.java b/driver/src/main/java/org/neo4j/driver/internal/cluster/MultiDatabasesRoutingProcedureRunner.java new file mode 100644 index 0000000000..3f05c5ee63 --- /dev/null +++ b/driver/src/main/java/org/neo4j/driver/internal/cluster/MultiDatabasesRoutingProcedureRunner.java @@ -0,0 +1,67 @@ +/* + * 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.cluster; + +import java.util.Objects; + +import org.neo4j.driver.AccessMode; +import org.neo4j.driver.Statement; +import org.neo4j.driver.internal.BookmarkHolder; +import org.neo4j.driver.internal.InternalBookmark; +import org.neo4j.driver.internal.ReadOnlyBookmarkHolder; +import org.neo4j.driver.internal.async.connection.DirectConnection; +import org.neo4j.driver.internal.spi.Connection; +import org.neo4j.driver.internal.util.ServerVersion; + +import static org.neo4j.driver.Values.parameters; +import static org.neo4j.driver.internal.messaging.request.MultiDatabaseUtil.ABSENT_DB_NAME; +import static org.neo4j.driver.internal.messaging.request.MultiDatabaseUtil.SYSTEM_DB_NAME; + +public class MultiDatabasesRoutingProcedureRunner extends RoutingProcedureRunner +{ + static final String DATABASE_NAME = "database"; + static final String MULTI_DB_GET_ROUTING_TABLE = String.format( "CALL dbms.routing.getRoutingTable($%s, $%s)", ROUTING_CONTEXT, DATABASE_NAME ); + + public MultiDatabasesRoutingProcedureRunner( RoutingContext context ) + { + super( context ); + } + + @Override + BookmarkHolder bookmarkHolder( InternalBookmark bookmark ) + { + return new ReadOnlyBookmarkHolder( bookmark ); + } + + @Override + Statement procedureStatement( ServerVersion serverVersion, String databaseName ) + { + if ( Objects.equals( ABSENT_DB_NAME, databaseName ) ) + { + databaseName = null; + } + return new Statement( MULTI_DB_GET_ROUTING_TABLE, parameters( ROUTING_CONTEXT, context.asMap(), DATABASE_NAME, databaseName ) ); + } + + @Override + DirectConnection connection( Connection connection ) + { + return new DirectConnection( connection, SYSTEM_DB_NAME, AccessMode.READ ); + } +} diff --git a/driver/src/main/java/org/neo4j/driver/internal/cluster/Rediscovery.java b/driver/src/main/java/org/neo4j/driver/internal/cluster/Rediscovery.java index 612675ae0b..e558edd867 100644 --- a/driver/src/main/java/org/neo4j/driver/internal/cluster/Rediscovery.java +++ b/driver/src/main/java/org/neo4j/driver/internal/cluster/Rediscovery.java @@ -20,9 +20,10 @@ import java.util.concurrent.CompletionStage; +import org.neo4j.driver.internal.InternalBookmark; import org.neo4j.driver.internal.spi.ConnectionPool; public interface Rediscovery { - CompletionStage lookupClusterComposition( RoutingTable routingTable, ConnectionPool connectionPool ); + CompletionStage lookupClusterComposition( RoutingTable routingTable, ConnectionPool connectionPool, InternalBookmark bookmark ); } diff --git a/driver/src/main/java/org/neo4j/driver/internal/cluster/RediscoveryImpl.java b/driver/src/main/java/org/neo4j/driver/internal/cluster/RediscoveryImpl.java index 1d9449ae7b..ca9fee871c 100644 --- a/driver/src/main/java/org/neo4j/driver/internal/cluster/RediscoveryImpl.java +++ b/driver/src/main/java/org/neo4j/driver/internal/cluster/RediscoveryImpl.java @@ -35,6 +35,7 @@ import org.neo4j.driver.exceptions.SecurityException; import org.neo4j.driver.exceptions.ServiceUnavailableException; import org.neo4j.driver.internal.BoltServerAddress; +import org.neo4j.driver.internal.InternalBookmark; import org.neo4j.driver.internal.spi.Connection; import org.neo4j.driver.internal.spi.ConnectionPool; import org.neo4j.driver.internal.util.Futures; @@ -82,17 +83,17 @@ public RediscoveryImpl( BoltServerAddress initialRouter, RoutingSettings setting * @return new cluster composition. */ @Override - public CompletionStage lookupClusterComposition( RoutingTable routingTable, ConnectionPool connectionPool ) + public CompletionStage lookupClusterComposition( RoutingTable routingTable, ConnectionPool connectionPool, InternalBookmark bookmark ) { CompletableFuture result = new CompletableFuture<>(); - lookupClusterComposition( routingTable, connectionPool, 0, 0, result ); + lookupClusterComposition( routingTable, connectionPool, 0, 0, result, bookmark ); return result; } private void lookupClusterComposition( RoutingTable routingTable, ConnectionPool pool, - int failures, long previousDelay, CompletableFuture result ) + int failures, long previousDelay, CompletableFuture result, InternalBookmark bookmark ) { - lookup( routingTable, pool ).whenComplete( ( composition, completionError ) -> + lookup( routingTable, pool, bookmark ).whenComplete( ( composition, completionError ) -> { Throwable error = Futures.completionExceptionCause( completionError ); if ( error != null ) @@ -115,7 +116,7 @@ else if ( composition != null ) long nextDelay = Math.max( settings.retryTimeoutDelay(), previousDelay * 2 ); logger.info( "Unable to fetch new routing table, will try again in " + nextDelay + "ms" ); eventExecutorGroup.next().schedule( - () -> lookupClusterComposition( routingTable, pool, newFailures, nextDelay, result ), + () -> lookupClusterComposition( routingTable, pool, newFailures, nextDelay, result, bookmark ), nextDelay, TimeUnit.MILLISECONDS ); } @@ -123,52 +124,52 @@ else if ( composition != null ) } ); } - private CompletionStage lookup( RoutingTable routingTable, ConnectionPool connectionPool ) + private CompletionStage lookup( RoutingTable routingTable, ConnectionPool connectionPool, InternalBookmark bookmark ) { CompletionStage compositionStage; if ( routingTable.preferInitialRouter() ) { - compositionStage = lookupOnInitialRouterThenOnKnownRouters( routingTable, connectionPool ); + compositionStage = lookupOnInitialRouterThenOnKnownRouters( routingTable, connectionPool, bookmark ); } else { - compositionStage = lookupOnKnownRoutersThenOnInitialRouter( routingTable, connectionPool ); + compositionStage = lookupOnKnownRoutersThenOnInitialRouter( routingTable, connectionPool, bookmark ); } return compositionStage; } private CompletionStage lookupOnKnownRoutersThenOnInitialRouter( RoutingTable routingTable, - ConnectionPool connectionPool ) + ConnectionPool connectionPool, InternalBookmark bookmark ) { Set seenServers = new HashSet<>(); - return lookupOnKnownRouters( routingTable, connectionPool, seenServers ).thenCompose( composition -> + return lookupOnKnownRouters( routingTable, connectionPool, seenServers, bookmark ).thenCompose( composition -> { if ( composition != null ) { return completedFuture( composition ); } - return lookupOnInitialRouter( routingTable, connectionPool, seenServers ); + return lookupOnInitialRouter( routingTable, connectionPool, seenServers, bookmark ); } ); } private CompletionStage lookupOnInitialRouterThenOnKnownRouters( RoutingTable routingTable, - ConnectionPool connectionPool ) + ConnectionPool connectionPool, InternalBookmark bookmark ) { Set seenServers = emptySet(); - return lookupOnInitialRouter( routingTable, connectionPool, seenServers ).thenCompose( composition -> + return lookupOnInitialRouter( routingTable, connectionPool, seenServers, bookmark ).thenCompose( composition -> { if ( composition != null ) { return completedFuture( composition ); } - return lookupOnKnownRouters( routingTable, connectionPool, new HashSet<>() ); + return lookupOnKnownRouters( routingTable, connectionPool, new HashSet<>(), bookmark ); } ); } private CompletionStage lookupOnKnownRouters( RoutingTable routingTable, - ConnectionPool connectionPool, Set seenServers ) + ConnectionPool connectionPool, Set seenServers, InternalBookmark bookmark ) { BoltServerAddress[] addresses = routingTable.routers().toArray(); @@ -183,7 +184,7 @@ private CompletionStage lookupOnKnownRouters( RoutingTable r } else { - return lookupOnRouter( address, routingTable, connectionPool ) + return lookupOnRouter( address, routingTable, connectionPool, bookmark ) .whenComplete( ( ignore, error ) -> seenServers.add( address ) ); } } ); @@ -192,7 +193,7 @@ private CompletionStage lookupOnKnownRouters( RoutingTable r } private CompletionStage lookupOnInitialRouter( RoutingTable routingTable, - ConnectionPool connectionPool, Set seenServers ) + ConnectionPool connectionPool, Set seenServers, InternalBookmark bookmark ) { List addresses; try @@ -214,29 +215,30 @@ private CompletionStage lookupOnInitialRouter( RoutingTable { return completedFuture( composition ); } - return lookupOnRouter( address, routingTable, connectionPool ); + return lookupOnRouter( address, routingTable, connectionPool, bookmark ); } ); } return result; } private CompletionStage lookupOnRouter( BoltServerAddress routerAddress, - RoutingTable routingTable, ConnectionPool connectionPool ) + RoutingTable routingTable, ConnectionPool connectionPool, InternalBookmark bookmark ) { CompletionStage connectionStage = connectionPool.acquire( routerAddress ); - return provider.getClusterComposition( connectionStage, routingTable.database() ).handle( ( response, error ) -> - { - Throwable cause = Futures.completionExceptionCause( error ); - if ( cause != null ) - { - return handleRoutingProcedureError( cause, routingTable, routerAddress ); - } - else - { - return response; - } - } ); + return connectionStage + .thenCompose( connection -> provider.getClusterComposition( connection, routingTable.database(), bookmark ) ) + .handle( ( response, error ) -> { + Throwable cause = Futures.completionExceptionCause( error ); + if ( cause != null ) + { + return handleRoutingProcedureError( cause, routingTable, routerAddress ); + } + else + { + return response; + } + } ); } private ClusterComposition handleRoutingProcedureError( Throwable error, RoutingTable routingTable, diff --git a/driver/src/main/java/org/neo4j/driver/internal/cluster/RoutingProcedureClusterCompositionProvider.java b/driver/src/main/java/org/neo4j/driver/internal/cluster/RoutingProcedureClusterCompositionProvider.java index 20788cc4c2..66e468f822 100644 --- a/driver/src/main/java/org/neo4j/driver/internal/cluster/RoutingProcedureClusterCompositionProvider.java +++ b/driver/src/main/java/org/neo4j/driver/internal/cluster/RoutingProcedureClusterCompositionProvider.java @@ -26,8 +26,10 @@ import org.neo4j.driver.Statement; import org.neo4j.driver.exceptions.ProtocolException; import org.neo4j.driver.exceptions.value.ValueException; +import org.neo4j.driver.internal.InternalBookmark; import org.neo4j.driver.internal.spi.Connection; import org.neo4j.driver.internal.util.Clock; +import org.neo4j.driver.internal.util.ServerVersion; import static java.lang.String.format; @@ -37,22 +39,35 @@ public class RoutingProcedureClusterCompositionProvider implements ClusterCompos private final Clock clock; private final RoutingProcedureRunner routingProcedureRunner; + private final RoutingProcedureRunner multiDatabaseRoutingProcedureRunner; public RoutingProcedureClusterCompositionProvider( Clock clock, RoutingContext routingContext ) { - this( clock, new RoutingProcedureRunner( routingContext ) ); + this( clock, new RoutingProcedureRunner( routingContext ), new MultiDatabasesRoutingProcedureRunner( routingContext ) ); } - RoutingProcedureClusterCompositionProvider( Clock clock, RoutingProcedureRunner routingProcedureRunner ) + RoutingProcedureClusterCompositionProvider( Clock clock, RoutingProcedureRunner routingProcedureRunner, + MultiDatabasesRoutingProcedureRunner multiDatabaseRoutingProcedureRunner ) { this.clock = clock; this.routingProcedureRunner = routingProcedureRunner; + this.multiDatabaseRoutingProcedureRunner = multiDatabaseRoutingProcedureRunner; } @Override - public CompletionStage getClusterComposition( CompletionStage connectionStage, String databaseName ) + public CompletionStage getClusterComposition( Connection connection, String databaseName, InternalBookmark bookmark ) { - return routingProcedureRunner.run( connectionStage, databaseName ) + RoutingProcedureRunner runner; + if ( connection.serverVersion().greaterThanOrEqual( ServerVersion.v4_0_0 ) ) + { + runner = multiDatabaseRoutingProcedureRunner; + } + else + { + runner = routingProcedureRunner; + } + + return runner.run( connection, databaseName, bookmark ) .thenApply( this::processRoutingResponse ); } 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 c708d60b8f..85d5189e36 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 @@ -30,7 +30,8 @@ import org.neo4j.driver.async.StatementResultCursor; import org.neo4j.driver.exceptions.ClientException; import org.neo4j.driver.exceptions.FatalDiscoveryException; -import org.neo4j.driver.internal.BookmarksHolder; +import org.neo4j.driver.internal.BookmarkHolder; +import org.neo4j.driver.internal.InternalBookmark; import org.neo4j.driver.internal.async.connection.DirectConnection; import org.neo4j.driver.internal.spi.Connection; import org.neo4j.driver.internal.util.Futures; @@ -38,82 +39,55 @@ import static org.neo4j.driver.Values.parameters; import static org.neo4j.driver.internal.messaging.request.MultiDatabaseUtil.ABSENT_DB_NAME; -import static org.neo4j.driver.internal.messaging.request.MultiDatabaseUtil.SYSTEM_DB_NAME; -import static org.neo4j.driver.internal.util.ServerVersion.v4_0_0; public class RoutingProcedureRunner { static final String ROUTING_CONTEXT = "context"; - static final String GET_ROUTING_TABLE = "dbms.cluster.routing.getRoutingTable($" + ROUTING_CONTEXT + ")"; - static final String DATABASE_NAME = "database"; - static final String MULTI_DB_GET_ROUTING_TABLE = String.format( "dbms.routing.getRoutingTable($%s, $%s)", ROUTING_CONTEXT, DATABASE_NAME ); + static final String GET_ROUTING_TABLE = "CALL dbms.cluster.routing.getRoutingTable($" + ROUTING_CONTEXT + ")"; - private final RoutingContext context; + final RoutingContext context; public RoutingProcedureRunner( RoutingContext context ) { this.context = context; } - public CompletionStage run( CompletionStage connectionStage, String databaseName ) + public CompletionStage run( Connection connection, String databaseName, InternalBookmark bookmark ) { - return connectionStage.thenCompose( connection -> - { - ServerVersion serverVersion = connection.serverVersion(); - // As the connection can connect to any router (a.k.a. any core members), this connection strictly speaking is a read connection. - DirectConnection delegate = connection( serverVersion, connection ); - Statement procedure = procedureStatement( serverVersion, databaseName ); - return runProcedure( delegate, procedure ) - .thenCompose( records -> releaseConnection( delegate, records ) ) - .handle( ( records, error ) -> processProcedureResponse( procedure, records, error ) ); - } ); + DirectConnection delegate = connection( connection ); + Statement procedure = procedureStatement( connection.serverVersion(), databaseName ); + BookmarkHolder bookmarkHolder = bookmarkHolder( bookmark ); + return runProcedure( delegate, procedure, bookmarkHolder ) + .thenCompose( records -> releaseConnection( delegate, records ) ) + .handle( ( records, error ) -> processProcedureResponse( procedure, records, error ) ); } - CompletionStage> runProcedure( Connection connection, Statement procedure ) + DirectConnection connection( Connection connection ) { - return connection.protocol() - .runInAutoCommitTransaction( connection, procedure, BookmarksHolder.NO_OP, TransactionConfig.empty(), true ) - .asyncResult().thenCompose( StatementResultCursor::listAsync ); + return new DirectConnection( connection, ABSENT_DB_NAME, AccessMode.WRITE ); } - private DirectConnection connection( ServerVersion serverVersion, Connection connection ) + Statement procedureStatement( ServerVersion serverVersion, String databaseName ) { - if ( serverVersion.greaterThanOrEqual( v4_0_0 )) + if ( !Objects.equals( ABSENT_DB_NAME, databaseName ) ) { - return new DirectConnection( connection, SYSTEM_DB_NAME, AccessMode.READ ); - } - else - { - return new DirectConnection( connection, ABSENT_DB_NAME, AccessMode.WRITE ); + throw new FatalDiscoveryException( String.format( + "Refreshing routing table for multi-databases is not supported in server version lower than 4.0. " + + "Current server version: %s. Database name: `%s`", serverVersion, databaseName ) ); } + return new Statement( GET_ROUTING_TABLE, parameters( ROUTING_CONTEXT, context.asMap() ) ); } - private Statement procedureStatement( ServerVersion serverVersion, String databaseName ) + BookmarkHolder bookmarkHolder( InternalBookmark ignored ) { - /* - * For v4.0+ databases, call procedure to get routing table for the database specified. - * For database version lower than 4.0, the database name will be ignored. - */ - if ( Objects.equals( ABSENT_DB_NAME, databaseName ) ) - { - databaseName = null; - } + return BookmarkHolder.NO_OP; + } - if ( serverVersion.greaterThanOrEqual( v4_0_0 ) ) - { - return new Statement( "CALL " + MULTI_DB_GET_ROUTING_TABLE, - parameters( ROUTING_CONTEXT, context.asMap(), DATABASE_NAME, databaseName ) ); - } - else - { - if ( databaseName != null ) - { - throw new FatalDiscoveryException( String.format( "Refreshing routing table for multi-databases is not supported in server version lower than 4.0. " + - "Current server version: %s. Database name: `%s`", serverVersion, databaseName ) ); - } - return new Statement( "CALL " + GET_ROUTING_TABLE, - parameters( ROUTING_CONTEXT, context.asMap() ) ); - } + CompletionStage> runProcedure( Connection connection, Statement procedure, BookmarkHolder bookmarkHolder ) + { + return connection.protocol() + .runInAutoCommitTransaction( connection, procedure, bookmarkHolder, TransactionConfig.empty(), true ) + .asyncResult().thenCompose( StatementResultCursor::listAsync ); } private CompletionStage> releaseConnection( Connection connection, List records ) @@ -126,7 +100,7 @@ private CompletionStage> releaseConnection( Connection connection, return connection.release().thenApply( ignore -> records ); } - private RoutingProcedureResponse processProcedureResponse( Statement procedure, List records, + private static RoutingProcedureResponse processProcedureResponse( Statement procedure, List records, Throwable error ) { Throwable cause = Futures.completionExceptionCause( error ); @@ -140,7 +114,7 @@ private RoutingProcedureResponse processProcedureResponse( Statement procedure, } } - private RoutingProcedureResponse handleError( Statement procedure, Throwable error ) + private static RoutingProcedureResponse handleError( Statement procedure, Throwable error ) { if ( error instanceof ClientException ) { diff --git a/driver/src/main/java/org/neo4j/driver/internal/cluster/RoutingTableHandler.java b/driver/src/main/java/org/neo4j/driver/internal/cluster/RoutingTableHandler.java index 50e426afb4..4239914523 100644 --- a/driver/src/main/java/org/neo4j/driver/internal/cluster/RoutingTableHandler.java +++ b/driver/src/main/java/org/neo4j/driver/internal/cluster/RoutingTableHandler.java @@ -22,10 +22,10 @@ import java.util.concurrent.CompletableFuture; import java.util.concurrent.CompletionStage; -import org.neo4j.driver.AccessMode; import org.neo4j.driver.Logger; import org.neo4j.driver.internal.BoltServerAddress; import org.neo4j.driver.internal.RoutingErrorHandler; +import org.neo4j.driver.internal.async.ConnectionContext; import org.neo4j.driver.internal.spi.ConnectionPool; import org.neo4j.driver.internal.util.Futures; @@ -67,14 +67,14 @@ public void onWriteFailure( BoltServerAddress address ) routingTable.forgetWriter( address ); } - synchronized CompletionStage refreshRoutingTable( AccessMode mode ) + synchronized CompletionStage refreshRoutingTable( ConnectionContext context ) { if ( refreshRoutingTableFuture != null ) { // refresh is already happening concurrently, just use it's result return refreshRoutingTableFuture; } - else if ( routingTable.isStaleFor( mode ) ) + else if ( routingTable.isStaleFor( context.mode() ) ) { // existing routing table is not fresh and should be updated log.info( "Routing table for database '%s' is stale. %s", databaseName, routingTable ); @@ -82,7 +82,7 @@ else if ( routingTable.isStaleFor( mode ) ) CompletableFuture resultFuture = new CompletableFuture<>(); refreshRoutingTableFuture = resultFuture; - rediscovery.lookupClusterComposition( routingTable, connectionPool ) + rediscovery.lookupClusterComposition( routingTable, connectionPool, context.rediscoveryBookmark() ) .whenComplete( ( composition, completionError ) -> { Throwable error = Futures.completionExceptionCause( completionError ); diff --git a/driver/src/main/java/org/neo4j/driver/internal/cluster/RoutingTableRegistry.java b/driver/src/main/java/org/neo4j/driver/internal/cluster/RoutingTableRegistry.java index d4bf878a5c..79bfdd6788 100644 --- a/driver/src/main/java/org/neo4j/driver/internal/cluster/RoutingTableRegistry.java +++ b/driver/src/main/java/org/neo4j/driver/internal/cluster/RoutingTableRegistry.java @@ -21,8 +21,8 @@ import java.util.Set; import java.util.concurrent.CompletionStage; -import org.neo4j.driver.AccessMode; import org.neo4j.driver.internal.BoltServerAddress; +import org.neo4j.driver.internal.async.ConnectionContext; /** * A generic interface to access all routing tables as a whole. @@ -35,7 +35,7 @@ public interface RoutingTableRegistry * For server version lower than 4.0, the database name will be ignored while refreshing routing table. * @return The future of a new routing table handler. */ - CompletionStage refreshRoutingTable( String databaseName, AccessMode mode ); + CompletionStage refreshRoutingTable( ConnectionContext context ); /** * @return all servers in the registry diff --git a/driver/src/main/java/org/neo4j/driver/internal/cluster/RoutingTableRegistryImpl.java b/driver/src/main/java/org/neo4j/driver/internal/cluster/RoutingTableRegistryImpl.java index 682ac6fb86..e0d65b8c92 100644 --- a/driver/src/main/java/org/neo4j/driver/internal/cluster/RoutingTableRegistryImpl.java +++ b/driver/src/main/java/org/neo4j/driver/internal/cluster/RoutingTableRegistryImpl.java @@ -24,9 +24,9 @@ import java.util.concurrent.ConcurrentHashMap; import java.util.concurrent.ConcurrentMap; -import org.neo4j.driver.AccessMode; import org.neo4j.driver.Logger; import org.neo4j.driver.internal.BoltServerAddress; +import org.neo4j.driver.internal.async.ConnectionContext; import org.neo4j.driver.internal.spi.ConnectionPool; import org.neo4j.driver.internal.util.Clock; @@ -49,10 +49,10 @@ public RoutingTableRegistryImpl( ConnectionPool connectionPool, Rediscovery redi } @Override - public CompletionStage refreshRoutingTable( String databaseName, AccessMode mode ) + public CompletionStage refreshRoutingTable( ConnectionContext context ) { - RoutingTableHandler handler = getOrCreate( databaseName ); - return handler.refreshRoutingTable( mode ).thenApply( ignored -> handler ); + RoutingTableHandler handler = getOrCreate( context.databaseName() ); + return handler.refreshRoutingTable( context ).thenApply( ignored -> handler ); } @Override diff --git a/driver/src/main/java/org/neo4j/driver/internal/cluster/loadbalancing/LoadBalancer.java b/driver/src/main/java/org/neo4j/driver/internal/cluster/loadbalancing/LoadBalancer.java index bc6c061de0..bdc8a15c01 100644 --- a/driver/src/main/java/org/neo4j/driver/internal/cluster/loadbalancing/LoadBalancer.java +++ b/driver/src/main/java/org/neo4j/driver/internal/cluster/loadbalancing/LoadBalancer.java @@ -29,6 +29,7 @@ import org.neo4j.driver.exceptions.ServiceUnavailableException; import org.neo4j.driver.exceptions.SessionExpiredException; import org.neo4j.driver.internal.BoltServerAddress; +import org.neo4j.driver.internal.async.ConnectionContext; import org.neo4j.driver.internal.async.connection.RoutingConnection; import org.neo4j.driver.internal.cluster.AddressSet; import org.neo4j.driver.internal.cluster.ClusterCompositionProvider; @@ -47,7 +48,7 @@ import org.neo4j.driver.net.ServerAddressResolver; import static java.lang.String.format; -import static org.neo4j.driver.internal.messaging.request.MultiDatabaseUtil.ABSENT_DB_NAME; +import static org.neo4j.driver.internal.async.ImmutableConnectionContext.simple; public class LoadBalancer implements ConnectionProvider { @@ -77,17 +78,17 @@ public LoadBalancer( BoltServerAddress initialRouter, RoutingSettings settings, } @Override - public CompletionStage acquireConnection( String databaseName, AccessMode mode ) + public CompletionStage acquireConnection( ConnectionContext context ) { - return routingTables.refreshRoutingTable( databaseName, mode ) - .thenCompose( handler -> acquire( mode, handler.routingTable() ) - .thenApply( connection -> new RoutingConnection( connection, databaseName, mode, handler ) ) ); + return routingTables.refreshRoutingTable( context ) + .thenCompose( handler -> acquire( context.mode(), handler.routingTable() ) + .thenApply( connection -> new RoutingConnection( connection, context.databaseName(), context.mode(), handler ) ) ); } @Override public CompletionStage verifyConnectivity() { - return routingTables.refreshRoutingTable( ABSENT_DB_NAME, AccessMode.READ ).handle( ( ignored, error ) -> { + return routingTables.refreshRoutingTable( simple() ).handle( ( ignored, error ) -> { if ( error != null ) { Throwable cause = Futures.completionExceptionCause( error ); diff --git a/driver/src/main/java/org/neo4j/driver/internal/handlers/CommitTxResponseHandler.java b/driver/src/main/java/org/neo4j/driver/internal/handlers/CommitTxResponseHandler.java index d2602f9fa6..132790220a 100644 --- a/driver/src/main/java/org/neo4j/driver/internal/handlers/CommitTxResponseHandler.java +++ b/driver/src/main/java/org/neo4j/driver/internal/handlers/CommitTxResponseHandler.java @@ -22,17 +22,17 @@ import java.util.Map; import java.util.concurrent.CompletableFuture; -import org.neo4j.driver.internal.Bookmarks; -import org.neo4j.driver.internal.spi.ResponseHandler; import org.neo4j.driver.Value; +import org.neo4j.driver.internal.InternalBookmark; +import org.neo4j.driver.internal.spi.ResponseHandler; import static java.util.Objects.requireNonNull; public class CommitTxResponseHandler implements ResponseHandler { - private final CompletableFuture commitFuture; + private final CompletableFuture commitFuture; - public CommitTxResponseHandler( CompletableFuture commitFuture ) + public CommitTxResponseHandler( CompletableFuture commitFuture ) { this.commitFuture = requireNonNull( commitFuture ); } @@ -47,7 +47,7 @@ public void onSuccess( Map metadata ) } else { - commitFuture.complete( Bookmarks.from( bookmarkValue.asString() ) ); + commitFuture.complete( InternalBookmark.parse( bookmarkValue.asString() ) ); } } 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 fef1eac382..934449bdde 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 @@ -19,7 +19,7 @@ package org.neo4j.driver.internal.handlers; import org.neo4j.driver.Statement; -import org.neo4j.driver.internal.BookmarksHolder; +import org.neo4j.driver.internal.BookmarkHolder; import org.neo4j.driver.internal.async.ExplicitTransaction; import org.neo4j.driver.internal.handlers.pulln.BasicPullResponseHandler; import org.neo4j.driver.internal.handlers.pulln.SessionPullResponseHandler; @@ -37,27 +37,27 @@ public static AbstractPullAllResponseHandler newBoltV1PullAllHandler( Statement { return new TransactionPullAllResponseHandler( statement, runHandler, connection, tx, BoltProtocolV1.METADATA_EXTRACTOR ); } - return new SessionPullAllResponseHandler( statement, runHandler, connection, BookmarksHolder.NO_OP, BoltProtocolV1.METADATA_EXTRACTOR ); + return new SessionPullAllResponseHandler( statement, runHandler, connection, BookmarkHolder.NO_OP, BoltProtocolV1.METADATA_EXTRACTOR ); } public static AbstractPullAllResponseHandler newBoltV3PullAllHandler( Statement statement, RunResponseHandler runHandler, Connection connection, - BookmarksHolder bookmarksHolder, ExplicitTransaction tx ) + BookmarkHolder bookmarkHolder, ExplicitTransaction tx ) { if ( tx != null ) { return new TransactionPullAllResponseHandler( statement, runHandler, connection, tx, BoltProtocolV3.METADATA_EXTRACTOR ); } - return new SessionPullAllResponseHandler( statement, runHandler, connection, bookmarksHolder, BoltProtocolV3.METADATA_EXTRACTOR ); + return new SessionPullAllResponseHandler( statement, runHandler, connection, bookmarkHolder, BoltProtocolV3.METADATA_EXTRACTOR ); } public static BasicPullResponseHandler newBoltV4PullHandler( Statement statement, RunResponseHandler runHandler, Connection connection, - BookmarksHolder bookmarksHolder, ExplicitTransaction tx ) + BookmarkHolder bookmarkHolder, ExplicitTransaction tx ) { if ( tx != null ) { return new TransactionPullResponseHandler( statement, runHandler, connection, tx, BoltProtocolV3.METADATA_EXTRACTOR ); } - return new SessionPullResponseHandler( statement, runHandler, connection, bookmarksHolder, BoltProtocolV3.METADATA_EXTRACTOR ); + return new SessionPullResponseHandler( statement, runHandler, connection, bookmarkHolder, BoltProtocolV3.METADATA_EXTRACTOR ); } } diff --git a/driver/src/main/java/org/neo4j/driver/internal/handlers/SessionPullAllResponseHandler.java b/driver/src/main/java/org/neo4j/driver/internal/handlers/SessionPullAllResponseHandler.java index 57de23da1a..81a396a074 100644 --- a/driver/src/main/java/org/neo4j/driver/internal/handlers/SessionPullAllResponseHandler.java +++ b/driver/src/main/java/org/neo4j/driver/internal/handlers/SessionPullAllResponseHandler.java @@ -20,7 +20,7 @@ import java.util.Map; -import org.neo4j.driver.internal.BookmarksHolder; +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; @@ -30,20 +30,20 @@ public class SessionPullAllResponseHandler extends AbstractPullAllResponseHandler { - private final BookmarksHolder bookmarksHolder; + private final BookmarkHolder bookmarkHolder; public SessionPullAllResponseHandler( Statement statement, RunResponseHandler runResponseHandler, - Connection connection, BookmarksHolder bookmarksHolder, MetadataExtractor metadataExtractor ) + Connection connection, BookmarkHolder bookmarkHolder, MetadataExtractor metadataExtractor ) { super( statement, runResponseHandler, connection, metadataExtractor ); - this.bookmarksHolder = requireNonNull( bookmarksHolder ); + this.bookmarkHolder = requireNonNull( bookmarkHolder ); } @Override protected void afterSuccess( Map metadata ) { releaseConnection(); - bookmarksHolder.setBookmarks( metadataExtractor.extractBookmarks( metadata ) ); + bookmarkHolder.setBookmark( metadataExtractor.extractBookmarks( metadata ) ); } @Override 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 index 14b6b8a559..5f2cebed4f 100644 --- 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 @@ -20,7 +20,7 @@ import java.util.Map; -import org.neo4j.driver.internal.BookmarksHolder; +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; @@ -31,20 +31,20 @@ public class SessionPullResponseHandler extends AbstractBasicPullResponseHandler { - private final BookmarksHolder bookmarksHolder; + private final BookmarkHolder bookmarkHolder; public SessionPullResponseHandler( Statement statement, RunResponseHandler runResponseHandler, - Connection connection, BookmarksHolder bookmarksHolder, MetadataExtractor metadataExtractor ) + Connection connection, BookmarkHolder bookmarkHolder, MetadataExtractor metadataExtractor ) { super( statement, runResponseHandler, connection, metadataExtractor ); - this.bookmarksHolder = requireNonNull( bookmarksHolder ); + this.bookmarkHolder = requireNonNull( bookmarkHolder ); } @Override protected void afterSuccess( Map metadata ) { releaseConnection(); - bookmarksHolder.setBookmarks( metadataExtractor.extractBookmarks( metadata ) ); + bookmarkHolder.setBookmark( metadataExtractor.extractBookmarks( metadata ) ); } @Override 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 c34b1c0d82..5255afc90b 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 @@ -30,8 +30,8 @@ import org.neo4j.driver.TransactionConfig; import org.neo4j.driver.Value; import org.neo4j.driver.exceptions.ClientException; -import org.neo4j.driver.internal.Bookmarks; -import org.neo4j.driver.internal.BookmarksHolder; +import org.neo4j.driver.internal.BookmarkHolder; +import org.neo4j.driver.internal.InternalBookmark; import org.neo4j.driver.internal.async.ExplicitTransaction; import org.neo4j.driver.internal.cursor.StatementResultCursorFactory; import org.neo4j.driver.internal.messaging.v1.BoltProtocolV1; @@ -70,11 +70,11 @@ public interface BoltProtocol * Begin an explicit transaction. * * @param connection the connection to use. - * @param bookmarks the bookmarks. Never null, should be {@link Bookmarks#empty()} when absent. + * @param bookmark the bookmarks. Never null, should be {@link InternalBookmark#empty()} when absent. * @param config the transaction configuration. Never null, should be {@link TransactionConfig#empty()} when absent. * @return a completion stage completed when transaction is started or completed exceptionally when there was a failure. */ - CompletionStage beginTransaction( Connection connection, Bookmarks bookmarks, TransactionConfig config ); + CompletionStage beginTransaction( Connection connection, InternalBookmark bookmark, TransactionConfig config ); /** * Commit the explicit transaction. @@ -82,7 +82,7 @@ public interface BoltProtocol * @param connection the connection to use. * @return a completion stage completed with a bookmark when transaction is committed or completed exceptionally when there was a failure. */ - CompletionStage commitTransaction( Connection connection ); + CompletionStage commitTransaction( Connection connection ); /** * Rollback the explicit transaction. @@ -97,7 +97,7 @@ public interface BoltProtocol * * @param connection the network connection to use. * @param statement the cypher to execute. - * @param bookmarksHolder the bookmarksHolder that keeps track of the current bookmark and can be updated with a new bookmark. + * @param bookmarkHolder the bookmarksHolder that keeps track of the current bookmark and can be updated with a new bookmark. * @param config the transaction config for the implicitly started auto-commit transaction. * @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 @@ -105,7 +105,7 @@ public interface BoltProtocol * @return stage with cursor. */ StatementResultCursorFactory runInAutoCommitTransaction( Connection connection, Statement statement, - BookmarksHolder bookmarksHolder, TransactionConfig config, boolean waitForRunResponse ); + BookmarkHolder bookmarkHolder, TransactionConfig config, boolean waitForRunResponse ); /** * Execute the given statement in a running explicit transaction, i.e. {@link Transaction#run(Statement)}. diff --git a/driver/src/main/java/org/neo4j/driver/internal/messaging/request/BeginMessage.java b/driver/src/main/java/org/neo4j/driver/internal/messaging/request/BeginMessage.java index cf5666e102..9546a43bc5 100644 --- a/driver/src/main/java/org/neo4j/driver/internal/messaging/request/BeginMessage.java +++ b/driver/src/main/java/org/neo4j/driver/internal/messaging/request/BeginMessage.java @@ -25,7 +25,7 @@ import org.neo4j.driver.AccessMode; import org.neo4j.driver.TransactionConfig; import org.neo4j.driver.Value; -import org.neo4j.driver.internal.Bookmarks; +import org.neo4j.driver.internal.InternalBookmark; import static org.neo4j.driver.internal.messaging.request.TransactionMetadataBuilder.buildMetadata; @@ -33,14 +33,14 @@ public class BeginMessage extends MessageWithMetadata { public static final byte SIGNATURE = 0x11; - public BeginMessage( Bookmarks bookmarks, TransactionConfig config, String databaseName, AccessMode mode ) + public BeginMessage( InternalBookmark bookmark, TransactionConfig config, String databaseName, AccessMode mode ) { - this( bookmarks, config.timeout(), config.metadata(), mode, databaseName ); + this( bookmark, config.timeout(), config.metadata(), mode, databaseName ); } - public BeginMessage( Bookmarks bookmarks, Duration txTimeout, Map txMetadata, AccessMode mode, String databaseName ) + public BeginMessage( InternalBookmark bookmark, Duration txTimeout, Map txMetadata, AccessMode mode, String databaseName ) { - super( buildMetadata( txTimeout, txMetadata, databaseName, mode, bookmarks ) ); + super( buildMetadata( txTimeout, txMetadata, databaseName, mode, bookmark ) ); } @Override diff --git a/driver/src/main/java/org/neo4j/driver/internal/messaging/request/RunWithMetadataMessage.java b/driver/src/main/java/org/neo4j/driver/internal/messaging/request/RunWithMetadataMessage.java index d460e4892a..45340d416b 100644 --- a/driver/src/main/java/org/neo4j/driver/internal/messaging/request/RunWithMetadataMessage.java +++ b/driver/src/main/java/org/neo4j/driver/internal/messaging/request/RunWithMetadataMessage.java @@ -26,7 +26,7 @@ import org.neo4j.driver.Statement; import org.neo4j.driver.TransactionConfig; import org.neo4j.driver.Value; -import org.neo4j.driver.internal.Bookmarks; +import org.neo4j.driver.internal.InternalBookmark; import static java.util.Collections.emptyMap; import static org.neo4j.driver.Values.ofValue; @@ -40,15 +40,15 @@ public class RunWithMetadataMessage extends MessageWithMetadata private final Map parameters; public static RunWithMetadataMessage autoCommitTxRunMessage( Statement statement, TransactionConfig config, String databaseName, AccessMode mode, - Bookmarks bookmarks ) + InternalBookmark bookmark ) { - return autoCommitTxRunMessage( statement, config.timeout(), config.metadata(), databaseName, mode, bookmarks ); + return autoCommitTxRunMessage( statement, config.timeout(), config.metadata(), databaseName, mode, bookmark ); } public static RunWithMetadataMessage autoCommitTxRunMessage( Statement statement, Duration txTimeout, Map txMetadata, String databaseName, - AccessMode mode, Bookmarks bookmarks ) + AccessMode mode, InternalBookmark bookmark ) { - Map metadata = buildMetadata( txTimeout, txMetadata, databaseName, mode, bookmarks ); + Map metadata = buildMetadata( txTimeout, txMetadata, databaseName, mode, bookmark ); return new RunWithMetadataMessage( statement.text(), statement.parameters().asMap( ofValue() ), metadata ); } diff --git a/driver/src/main/java/org/neo4j/driver/internal/messaging/request/TransactionMetadataBuilder.java b/driver/src/main/java/org/neo4j/driver/internal/messaging/request/TransactionMetadataBuilder.java index d6403089ce..919175d950 100644 --- a/driver/src/main/java/org/neo4j/driver/internal/messaging/request/TransactionMetadataBuilder.java +++ b/driver/src/main/java/org/neo4j/driver/internal/messaging/request/TransactionMetadataBuilder.java @@ -23,7 +23,7 @@ import org.neo4j.driver.AccessMode; import org.neo4j.driver.Value; -import org.neo4j.driver.internal.Bookmarks; +import org.neo4j.driver.internal.InternalBookmark; import org.neo4j.driver.internal.util.Iterables; import static java.util.Collections.emptyMap; @@ -39,14 +39,14 @@ public class TransactionMetadataBuilder private static final String MODE_KEY = "mode"; private static final String MODE_READ_VALUE = "r"; - public static Map buildMetadata( Duration txTimeout, Map txMetadata, AccessMode mode, Bookmarks bookmarks ) + public static Map buildMetadata( Duration txTimeout, Map txMetadata, AccessMode mode, InternalBookmark bookmark ) { - return buildMetadata( txTimeout, txMetadata, ABSENT_DB_NAME, mode, bookmarks ); + return buildMetadata( txTimeout, txMetadata, ABSENT_DB_NAME, mode, bookmark ); } - public static Map buildMetadata( Duration txTimeout, Map txMetadata, String databaseName, AccessMode mode, Bookmarks bookmarks ) + public static Map buildMetadata( Duration txTimeout, Map txMetadata, String databaseName, AccessMode mode, InternalBookmark bookmark ) { - boolean bookmarksPresent = bookmarks != null && !bookmarks.isEmpty(); + boolean bookmarksPresent = bookmark != null && !bookmark.isEmpty(); boolean txTimeoutPresent = txTimeout != null; boolean txMetadataPresent = txMetadata != null && !txMetadata.isEmpty(); boolean accessModePresent = mode == AccessMode.READ; @@ -61,7 +61,7 @@ public static Map buildMetadata( Duration txTimeout, Map beginTransaction( Connection connection, Bookmarks bookmarks, TransactionConfig config ) + public CompletionStage beginTransaction( Connection connection, InternalBookmark bookmark, TransactionConfig config ) { try { @@ -106,7 +110,7 @@ public CompletionStage beginTransaction( Connection connection, Bookmarks return Futures.failedFuture( error ); } - if ( bookmarks.isEmpty() ) + if ( bookmark.isEmpty() ) { connection.write( BEGIN_MESSAGE, NoOpResponseHandler.INSTANCE, @@ -118,17 +122,19 @@ public CompletionStage beginTransaction( Connection connection, Bookmarks { CompletableFuture beginTxFuture = new CompletableFuture<>(); connection.writeAndFlush( - new RunMessage( BEGIN_QUERY, bookmarks.asBeginTransactionParameters() ), NoOpResponseHandler.INSTANCE, + new RunMessage( BEGIN_QUERY, SingleBookmarkHelper.asBeginTransactionParameters( bookmark ) ), NoOpResponseHandler.INSTANCE, PullAllMessage.PULL_ALL, new BeginTxResponseHandler( beginTxFuture ) ); return beginTxFuture; } } + + @Override - public CompletionStage commitTransaction( Connection connection ) + public CompletionStage commitTransaction( Connection connection ) { - CompletableFuture commitFuture = new CompletableFuture<>(); + CompletableFuture commitFuture = new CompletableFuture<>(); ResponseHandler pullAllHandler = new CommitTxResponseHandler( commitFuture ); connection.writeAndFlush( @@ -153,7 +159,7 @@ public CompletionStage rollbackTransaction( Connection connection ) @Override public StatementResultCursorFactory runInAutoCommitTransaction( Connection connection, Statement statement, - BookmarksHolder bookmarksHolder, TransactionConfig config, boolean waitForRunResponse ) + BookmarkHolder bookmarkHolder, TransactionConfig config, boolean waitForRunResponse ) { // bookmarks are ignored for auto-commit transactions in this version of the protocol verifyBeforeTransaction( config, connection.databaseName() ); @@ -200,4 +206,75 @@ private static ClientException txConfigNotSupported() return new ClientException( "Driver is connected to the database that does not support transaction configuration. " + "Please upgrade to neo4j 3.5.0 or later in order to use this functionality" ); } + + static class SingleBookmarkHelper + { + private static final String BOOKMARK_PREFIX = "neo4j:bookmark:v1:tx"; + private static final long UNKNOWN_BOOKMARK_VALUE = -1; + + static Map asBeginTransactionParameters( InternalBookmark bookmark ) + { + if ( bookmark.isEmpty() ) + { + return emptyMap(); + } + + // Driver sends {bookmark: "max", bookmarks: ["one", "two", "max"]} instead of simple + // {bookmarks: ["one", "two", "max"]} for backwards compatibility reasons. Old servers can only accept single + // bookmark that is why driver has to parse and compare given list of bookmarks. This functionality will + // eventually be removed. + Map parameters = newHashMapWithSize( 1 ); + parameters.put( "bookmark", value( maxBookmark( bookmark.values() ) ) ); + parameters.put( "bookmarks", value( bookmark.values() ) ); + return parameters; + } + + private static String maxBookmark( Iterable bookmarks ) + { + if ( bookmarks == null ) + { + return null; + } + + Iterator iterator = bookmarks.iterator(); + + if ( !iterator.hasNext() ) + { + return null; + } + + String maxBookmark = iterator.next(); + long maxValue = bookmarkValue( maxBookmark ); + + while ( iterator.hasNext() ) + { + String bookmark = iterator.next(); + long value = bookmarkValue( bookmark ); + + if ( value > maxValue ) + { + maxBookmark = bookmark; + maxValue = value; + } + } + + return maxBookmark; + } + + private static long bookmarkValue( String value ) + { + if ( value != null && value.startsWith( BOOKMARK_PREFIX ) ) + { + try + { + return Long.parseLong( value.substring( BOOKMARK_PREFIX.length() ) ); + } + catch ( NumberFormatException e ) + { + return UNKNOWN_BOOKMARK_VALUE; + } + } + return UNKNOWN_BOOKMARK_VALUE; + } + } } 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 1fa439f571..6a03b2721c 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 @@ -28,8 +28,8 @@ import org.neo4j.driver.Statement; import org.neo4j.driver.TransactionConfig; import org.neo4j.driver.Value; -import org.neo4j.driver.internal.Bookmarks; -import org.neo4j.driver.internal.BookmarksHolder; +import org.neo4j.driver.internal.BookmarkHolder; +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.StatementResultCursorFactory; @@ -93,7 +93,7 @@ public void prepareToCloseChannel( Channel channel ) } @Override - public CompletionStage beginTransaction( Connection connection, Bookmarks bookmarks, TransactionConfig config ) + public CompletionStage beginTransaction( Connection connection, InternalBookmark bookmark, TransactionConfig config ) { try { @@ -104,9 +104,9 @@ public CompletionStage beginTransaction( Connection connection, Bookmarks return Futures.failedFuture( error ); } - BeginMessage beginMessage = new BeginMessage( bookmarks, config, connection.databaseName(), connection.mode() ); + BeginMessage beginMessage = new BeginMessage( bookmark, config, connection.databaseName(), connection.mode() ); - if ( bookmarks.isEmpty() ) + if ( bookmark.isEmpty() ) { connection.write( beginMessage, NoOpResponseHandler.INSTANCE ); return Futures.completedWithNull(); @@ -120,9 +120,9 @@ public CompletionStage beginTransaction( Connection connection, Bookmarks } @Override - public CompletionStage commitTransaction( Connection connection ) + public CompletionStage commitTransaction( Connection connection ) { - CompletableFuture commitFuture = new CompletableFuture<>(); + CompletableFuture commitFuture = new CompletableFuture<>(); connection.writeAndFlush( COMMIT, new CommitTxResponseHandler( commitFuture ) ); return commitFuture; } @@ -137,12 +137,12 @@ public CompletionStage rollbackTransaction( Connection connection ) @Override public StatementResultCursorFactory runInAutoCommitTransaction( Connection connection, Statement statement, - BookmarksHolder bookmarksHolder, TransactionConfig config, boolean waitForRunResponse ) + BookmarkHolder bookmarkHolder, TransactionConfig config, boolean waitForRunResponse ) { verifyDatabaseNameBeforeTransaction( connection.databaseName() ); RunWithMetadataMessage runMessage = - autoCommitTxRunMessage( statement, config, connection.databaseName(), connection.mode(), bookmarksHolder.getBookmarks() ); - return buildResultCursorFactory( connection, statement, bookmarksHolder, null, runMessage, waitForRunResponse ); + autoCommitTxRunMessage( statement, config, connection.databaseName(), connection.mode(), bookmarkHolder.getBookmark() ); + return buildResultCursorFactory( connection, statement, bookmarkHolder, null, runMessage, waitForRunResponse ); } @Override @@ -150,14 +150,14 @@ public StatementResultCursorFactory runInExplicitTransaction( Connection connect boolean waitForRunResponse ) { RunWithMetadataMessage runMessage = explicitTxRunMessage( statement ); - return buildResultCursorFactory( connection, statement, BookmarksHolder.NO_OP, tx, runMessage, waitForRunResponse ); + return buildResultCursorFactory( connection, statement, BookmarkHolder.NO_OP, tx, runMessage, waitForRunResponse ); } - protected StatementResultCursorFactory buildResultCursorFactory( Connection connection, Statement statement, BookmarksHolder bookmarksHolder, + protected StatementResultCursorFactory buildResultCursorFactory( Connection connection, Statement statement, BookmarkHolder bookmarkHolder, ExplicitTransaction tx, RunWithMetadataMessage runMessage, boolean waitForRunResponse ) { RunResponseHandler runHandler = new RunResponseHandler( METADATA_EXTRACTOR ); - AbstractPullAllResponseHandler pullHandler = newBoltV3PullAllHandler( statement, runHandler, connection, bookmarksHolder, tx ); + AbstractPullAllResponseHandler pullHandler = newBoltV3PullAllHandler( statement, runHandler, connection, bookmarkHolder, tx ); return new AsyncResultCursorOnlyFactory( connection, runMessage, runHandler, pullHandler, waitForRunResponse ); } 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 9a2b33b20d..3d13559172 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 @@ -19,7 +19,7 @@ package org.neo4j.driver.internal.messaging.v4; import org.neo4j.driver.Statement; -import org.neo4j.driver.internal.BookmarksHolder; +import org.neo4j.driver.internal.BookmarkHolder; import org.neo4j.driver.internal.async.ExplicitTransaction; import org.neo4j.driver.internal.cursor.InternalStatementResultCursorFactory; import org.neo4j.driver.internal.cursor.StatementResultCursorFactory; @@ -47,13 +47,13 @@ public MessageFormat createMessageFormat() } @Override - protected StatementResultCursorFactory buildResultCursorFactory( Connection connection, Statement statement, BookmarksHolder bookmarksHolder, + protected StatementResultCursorFactory buildResultCursorFactory( Connection connection, Statement statement, BookmarkHolder bookmarkHolder, ExplicitTransaction tx, RunWithMetadataMessage runMessage, boolean waitForRunResponse ) { RunResponseHandler runHandler = new RunResponseHandler( METADATA_EXTRACTOR ); - AbstractPullAllResponseHandler pullAllHandler = newBoltV3PullAllHandler( statement, runHandler, connection, bookmarksHolder, tx ); - BasicPullResponseHandler pullHandler = newBoltV4PullHandler( statement, runHandler, connection, bookmarksHolder, tx ); + AbstractPullAllResponseHandler pullAllHandler = newBoltV3PullAllHandler( statement, runHandler, connection, bookmarkHolder, tx ); + BasicPullResponseHandler pullHandler = newBoltV4PullHandler( statement, runHandler, connection, bookmarkHolder, tx ); return new InternalStatementResultCursorFactory( connection, runMessage, runHandler, pullHandler, pullAllHandler, waitForRunResponse ); } diff --git a/driver/src/main/java/org/neo4j/driver/internal/reactive/InternalRxSession.java b/driver/src/main/java/org/neo4j/driver/internal/reactive/InternalRxSession.java index ea877d7243..2e0eba621d 100644 --- a/driver/src/main/java/org/neo4j/driver/internal/reactive/InternalRxSession.java +++ b/driver/src/main/java/org/neo4j/driver/internal/reactive/InternalRxSession.java @@ -27,6 +27,7 @@ import org.neo4j.driver.AccessMode; import org.neo4j.driver.Statement; import org.neo4j.driver.TransactionConfig; +import org.neo4j.driver.internal.Bookmark; import org.neo4j.driver.internal.async.NetworkSession; import org.neo4j.driver.internal.cursor.RxStatementResultCursor; import org.neo4j.driver.internal.util.Futures; @@ -175,7 +176,7 @@ private void releaseConnectionBeforeReturning( CompletableFuture returnFu } @Override - public String lastBookmark() + public Bookmark lastBookmark() { return session.lastBookmark(); } diff --git a/driver/src/main/java/org/neo4j/driver/internal/spi/ConnectionProvider.java b/driver/src/main/java/org/neo4j/driver/internal/spi/ConnectionProvider.java index 78d19cf887..e9224da102 100644 --- a/driver/src/main/java/org/neo4j/driver/internal/spi/ConnectionProvider.java +++ b/driver/src/main/java/org/neo4j/driver/internal/spi/ConnectionProvider.java @@ -20,7 +20,7 @@ import java.util.concurrent.CompletionStage; -import org.neo4j.driver.AccessMode; +import org.neo4j.driver.internal.async.ConnectionContext; /** * Interface defines a layer used by the driver to obtain connections. It is meant to be the only component that @@ -28,7 +28,7 @@ */ public interface ConnectionProvider { - CompletionStage acquireConnection( String databaseName, AccessMode mode ); + CompletionStage acquireConnection( ConnectionContext context ); /** * The validation of connectivity will happen with the default database. diff --git a/driver/src/main/java/org/neo4j/driver/internal/util/MetadataExtractor.java b/driver/src/main/java/org/neo4j/driver/internal/util/MetadataExtractor.java index e68aca9648..e708bf44e1 100644 --- a/driver/src/main/java/org/neo4j/driver/internal/util/MetadataExtractor.java +++ b/driver/src/main/java/org/neo4j/driver/internal/util/MetadataExtractor.java @@ -26,7 +26,7 @@ import org.neo4j.driver.Statement; import org.neo4j.driver.Value; import org.neo4j.driver.exceptions.UntrustedServerException; -import org.neo4j.driver.internal.Bookmarks; +import org.neo4j.driver.internal.InternalBookmark; import org.neo4j.driver.internal.spi.Connection; import org.neo4j.driver.internal.summary.InternalDatabaseInfo; import org.neo4j.driver.internal.summary.InternalNotification; @@ -121,14 +121,14 @@ public static DatabaseInfo extractDatabaseInfo( Map metadata ) } } - public static Bookmarks extractBookmarks( Map metadata ) + public static InternalBookmark extractBookmarks( Map metadata ) { Value bookmarkValue = metadata.get( "bookmark" ); if ( bookmarkValue != null && !bookmarkValue.isNull() && bookmarkValue.hasType( TYPE_SYSTEM.STRING() ) ) { - return Bookmarks.from( bookmarkValue.asString() ); + return InternalBookmark.parse( bookmarkValue.asString() ); } - return Bookmarks.empty(); + return InternalBookmark.empty(); } public static ServerVersion extractNeo4jServerVersion( Map metadata ) diff --git a/driver/src/main/java/org/neo4j/driver/reactive/RxSession.java b/driver/src/main/java/org/neo4j/driver/reactive/RxSession.java index 69a394adee..9c2cd53c0e 100644 --- a/driver/src/main/java/org/neo4j/driver/reactive/RxSession.java +++ b/driver/src/main/java/org/neo4j/driver/reactive/RxSession.java @@ -28,6 +28,7 @@ import org.neo4j.driver.Statement; import org.neo4j.driver.TransactionConfig; import org.neo4j.driver.Values; +import org.neo4j.driver.internal.Bookmark; /** * A reactive session is the same as {@link Session} except it provides a reactive API. @@ -231,7 +232,7 @@ public interface RxSession extends RxStatementRunner * * @return a reference to a previous transaction. */ - String lastBookmark(); + Bookmark lastBookmark(); /** * Signal that you are done using this session. diff --git a/driver/src/test/java/org/neo4j/driver/ParametersTest.java b/driver/src/test/java/org/neo4j/driver/ParametersTest.java index ead3036281..a1e2888e15 100644 --- a/driver/src/test/java/org/neo4j/driver/ParametersTest.java +++ b/driver/src/test/java/org/neo4j/driver/ParametersTest.java @@ -25,7 +25,7 @@ import java.util.stream.Stream; import org.neo4j.driver.exceptions.ClientException; -import org.neo4j.driver.internal.DefaultBookmarksHolder; +import org.neo4j.driver.internal.DefaultBookmarkHolder; import org.neo4j.driver.internal.InternalRecord; import org.neo4j.driver.internal.InternalSession; import org.neo4j.driver.internal.async.NetworkSession; @@ -109,7 +109,7 @@ private Session mockedSession() ConnectionProvider provider = mock( ConnectionProvider.class ); RetryLogic retryLogic = mock( RetryLogic.class ); NetworkSession session = - new NetworkSession( provider, retryLogic, ABSENT_DB_NAME, AccessMode.WRITE, new DefaultBookmarksHolder(), DEV_NULL_LOGGING ); + new NetworkSession( provider, retryLogic, ABSENT_DB_NAME, AccessMode.WRITE, new DefaultBookmarkHolder(), 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 2a5b33e746..1c737021cb 100644 --- a/driver/src/test/java/org/neo4j/driver/SessionConfigTest.java +++ b/driver/src/test/java/org/neo4j/driver/SessionConfigTest.java @@ -27,6 +27,8 @@ import java.util.Arrays; import java.util.List; +import org.neo4j.driver.internal.Bookmark; + import static java.util.Collections.emptyList; import static org.hamcrest.CoreMatchers.equalTo; import static org.junit.Assert.assertEquals; @@ -37,6 +39,7 @@ import static org.junit.jupiter.api.Assertions.assertTrue; import static org.neo4j.driver.SessionConfig.builder; import static org.neo4j.driver.SessionConfig.defaultConfig; +import static org.neo4j.driver.internal.InternalBookmark.parse; import static org.neo4j.driver.internal.messaging.request.MultiDatabaseUtil.ABSENT_DB_NAME; class SessionConfigTest @@ -44,28 +47,28 @@ class SessionConfigTest @Test void shouldReturnDefaultValues() throws Throwable { - SessionConfig parameters = defaultConfig(); + SessionConfig config = defaultConfig(); - Assert.assertEquals( AccessMode.WRITE, parameters.defaultAccessMode() ); - assertFalse( parameters.database().isPresent() ); - assertNull( parameters.bookmarks() ); + Assert.assertEquals( AccessMode.WRITE, config.defaultAccessMode() ); + assertFalse( config.database().isPresent() ); + assertNull( config.bookmarks() ); } @ParameterizedTest @EnumSource( AccessMode.class ) void shouldChangeAccessMode( AccessMode mode ) throws Throwable { - SessionConfig parameters = builder().withDefaultAccessMode( mode ).build(); - assertEquals( mode, parameters.defaultAccessMode() ); + SessionConfig config = builder().withDefaultAccessMode( mode ).build(); + assertEquals( mode, config.defaultAccessMode() ); } @ParameterizedTest @ValueSource( strings = {"foo", "data", "my awesome database", " "} ) void shouldChangeDatabaseName( String databaseName ) { - SessionConfig parameters = builder().withDatabase( databaseName ).build(); - assertTrue( parameters.database().isPresent() ); - assertEquals( databaseName, parameters.database().get() ); + SessionConfig config = builder().withDatabase( databaseName ).build(); + assertTrue( config.database().isPresent() ); + assertEquals( databaseName, config.database().get() ); } @Test @@ -85,30 +88,44 @@ void shouldForbiddenEmptyStringDatabaseName( String databaseName ) throws Throwa @Test void shouldAcceptNullBookmarks() throws Throwable { - SessionConfig parameters = builder().withBookmarks( (String[]) null ).build(); - assertNull( parameters.bookmarks() ); + SessionConfig config = builder().withBookmarks( (Bookmark[]) null ).build(); + assertNull( config.bookmarks() ); - SessionConfig parameters2 = builder().withBookmarks( (List) null ).build(); - assertNull( parameters2.bookmarks() ); + SessionConfig config2 = builder().withBookmarks( (List) null ).build(); + assertNull( config2.bookmarks() ); } @Test void shouldAcceptEmptyBookmarks() throws Throwable { - SessionConfig parameters = builder().withBookmarks().build(); - assertEquals( emptyList(), parameters.bookmarks() ); + SessionConfig config = builder().withBookmarks().build(); + assertEquals( emptyList(), config.bookmarks() ); - SessionConfig parameters2 = builder().withBookmarks( emptyList() ).build(); - assertEquals( emptyList(), parameters2.bookmarks() ); + SessionConfig config2 = builder().withBookmarks( emptyList() ).build(); + assertEquals( emptyList(), config2.bookmarks() ); } @Test void shouldAcceptBookmarks() throws Throwable { - SessionConfig parameters = builder().withBookmarks( "one", "two" ).build(); - assertThat( parameters.bookmarks(), equalTo( Arrays.asList( "one", "two" ) ) ); + Bookmark one = parse( "one" ); + Bookmark two = parse( "two" ); + SessionConfig config = builder().withBookmarks( one, two ).build(); + assertThat( config.bookmarks(), equalTo( Arrays.asList( one, two ) ) ); + + SessionConfig config2 = builder().withBookmarks( Arrays.asList( one, two ) ).build(); + assertThat( config2.bookmarks(), equalTo( Arrays.asList( one, two ) ) ); + } + + @Test + void shouldAcceptNullInBookmarks() throws Throwable + { + Bookmark one = parse( "one" ); + Bookmark two = parse( "two" ); + SessionConfig config = builder().withBookmarks( one, two, null ).build(); + assertThat( config.bookmarks(), equalTo( Arrays.asList( one, two, null ) ) ); - SessionConfig parameters2 = builder().withBookmarks( Arrays.asList( "one", "two" ) ).build(); - assertThat( parameters2.bookmarks(), equalTo( Arrays.asList( "one", "two" ) ) ); + SessionConfig config2 = builder().withBookmarks( Arrays.asList( one, two, null ) ).build(); + assertThat( config2.bookmarks(), equalTo( Arrays.asList( one, two, null ) ) ); } } 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 792d500b2d..35d612c6d0 100644 --- a/driver/src/test/java/org/neo4j/driver/integration/BookmarkIT.java +++ b/driver/src/test/java/org/neo4j/driver/integration/BookmarkIT.java @@ -22,23 +22,26 @@ import org.junit.jupiter.api.Test; import org.junit.jupiter.api.extension.RegisterExtension; -import java.util.HashSet; - import org.neo4j.driver.Driver; import org.neo4j.driver.Session; import org.neo4j.driver.Transaction; import org.neo4j.driver.exceptions.ClientException; +import org.neo4j.driver.internal.Bookmark; +import org.neo4j.driver.internal.util.DisabledOnNeo4jWith; +import org.neo4j.driver.internal.util.EnabledOnNeo4jWith; +import org.neo4j.driver.internal.util.Neo4jFeature; import org.neo4j.driver.util.ParallelizableIT; import org.neo4j.driver.util.SessionExtension; -import static java.util.Arrays.asList; +import static org.hamcrest.Matchers.not; import static org.hamcrest.Matchers.startsWith; -import static org.hamcrest.junit.MatcherAssert.assertThat; import static org.junit.jupiter.api.Assertions.assertEquals; -import static org.junit.jupiter.api.Assertions.assertNotNull; -import static org.junit.jupiter.api.Assertions.assertNull; import static org.junit.jupiter.api.Assertions.assertThrows; import static org.neo4j.driver.SessionConfig.builder; +import static org.neo4j.driver.internal.InternalBookmark.parse; +import static org.neo4j.driver.internal.util.BookmarkUtils.assertBookmarkContainsSingleValue; +import static org.neo4j.driver.internal.util.BookmarkUtils.assertBookmarkIsEmpty; +import static org.neo4j.driver.internal.util.BookmarkUtils.assertBookmarksContainsSingleUniqueValues; @ParallelizableIT class BookmarkIT @@ -57,23 +60,38 @@ void assumeBookmarkSupport() } @Test + @DisabledOnNeo4jWith( Neo4jFeature.BOLT_V4 ) void shouldReceiveBookmarkOnSuccessfulCommit() throws Throwable { // Given - assertNull( session.lastBookmark() ); + assertBookmarkIsEmpty( session.lastBookmark() ); + + // When + createNodeInTx( session ); + + // Then + assertBookmarkContainsSingleValue( session.lastBookmark(), startsWith( "neo4j:bookmark:v1:tx" ) ); + } + + @Test + @EnabledOnNeo4jWith( Neo4jFeature.BOLT_V4 ) + void shouldReceiveNewBookmarkOnSuccessfulCommit() throws Throwable + { + // Given + assertBookmarkIsEmpty( session.lastBookmark() ); // When createNodeInTx( session ); // Then - assertNotNull( session.lastBookmark() ); - assertThat( session.lastBookmark(), startsWith( "neo4j:bookmark:v1:tx" ) ); + assertBookmarkContainsSingleValue( session.lastBookmark(), startsWith( "neo4j:" ) ); + assertBookmarkContainsSingleValue( session.lastBookmark(), not( startsWith( "neo4j:bookmark:v1:tx" ) ) ); } @Test void shouldThrowForInvalidBookmark() { - String invalidBookmark = "hi, this is an invalid bookmark"; + Bookmark invalidBookmark = parse( "hi, this is an invalid bookmark" ); try ( Session session = driver.session( builder().withBookmarks( invalidBookmark ).build() ) ) { @@ -84,12 +102,12 @@ void shouldThrowForInvalidBookmark() @Test void bookmarkRemainsAfterRolledBackTx() { - assertNull( session.lastBookmark() ); + assertBookmarkIsEmpty( session.lastBookmark() ); createNodeInTx( session ); - String bookmark = session.lastBookmark(); - assertNotNull( bookmark ); + Bookmark bookmark = session.lastBookmark(); + assertBookmarkContainsSingleValue( bookmark ); try ( Transaction tx = session.beginTransaction() ) { @@ -103,12 +121,12 @@ void bookmarkRemainsAfterRolledBackTx() @Test void bookmarkRemainsAfterTxFailure() { - assertNull( session.lastBookmark() ); + assertBookmarkIsEmpty( session.lastBookmark() ); createNodeInTx( session ); - String bookmark = session.lastBookmark(); - assertNotNull( bookmark ); + Bookmark bookmark = session.lastBookmark(); + assertBookmarkContainsSingleValue( bookmark ); Transaction tx = session.beginTransaction(); tx.run( "RETURN" ); @@ -121,12 +139,12 @@ void bookmarkRemainsAfterTxFailure() @Test void bookmarkRemainsAfterSuccessfulSessionRun() { - assertNull( session.lastBookmark() ); + assertBookmarkIsEmpty( session.lastBookmark() ); createNodeInTx( session ); - String bookmark = session.lastBookmark(); - assertNotNull( bookmark ); + Bookmark bookmark = session.lastBookmark(); + assertBookmarkContainsSingleValue( bookmark ); session.run( "RETURN 1" ).consume(); @@ -136,12 +154,12 @@ void bookmarkRemainsAfterSuccessfulSessionRun() @Test void bookmarkRemainsAfterFailedSessionRun() { - assertNull( session.lastBookmark() ); + assertBookmarkIsEmpty( session.lastBookmark() ); createNodeInTx( session ); - String bookmark = session.lastBookmark(); - assertNotNull( bookmark ); + Bookmark bookmark = session.lastBookmark(); + assertBookmarkContainsSingleValue( bookmark ); assertThrows( ClientException.class, () -> session.run( "RETURN" ).consume() ); assertEquals( bookmark, session.lastBookmark() ); @@ -150,27 +168,27 @@ void bookmarkRemainsAfterFailedSessionRun() @Test void bookmarkIsUpdatedOnEveryCommittedTx() { - assertNull( session.lastBookmark() ); + assertBookmarkIsEmpty( session.lastBookmark() ); createNodeInTx( session ); - String bookmark1 = session.lastBookmark(); - assertNotNull( bookmark1 ); + Bookmark bookmark1 = session.lastBookmark(); + assertBookmarkContainsSingleValue( bookmark1 ); createNodeInTx( session ); - String bookmark2 = session.lastBookmark(); - assertNotNull( bookmark2 ); + Bookmark bookmark2 = session.lastBookmark(); + assertBookmarkContainsSingleValue( bookmark2 ); createNodeInTx( session ); - String bookmark3 = session.lastBookmark(); - assertNotNull( bookmark3 ); + Bookmark bookmark3 = session.lastBookmark(); + assertBookmarkContainsSingleValue( bookmark3 ); - assertEquals( 3, new HashSet<>( asList( bookmark1, bookmark2, bookmark3 ) ).size() ); + assertBookmarksContainsSingleUniqueValues( bookmark1, bookmark2, bookmark3 ); } @Test void createSessionWithInitialBookmark() { - String bookmark = "TheBookmark"; + Bookmark bookmark = parse( "TheBookmark" ); try ( Session session = driver.session( builder().withBookmarks( bookmark ).build() ) ) { assertEquals( bookmark, session.lastBookmark() ); @@ -180,7 +198,7 @@ void createSessionWithInitialBookmark() @Test void createSessionWithAccessModeAndInitialBookmark() { - String bookmark = "TheBookmark"; + Bookmark bookmark = parse( "TheBookmark" ); try ( Session session = driver.session( builder().withBookmarks( bookmark ).build() ) ) { assertEquals( bookmark, session.lastBookmark() ); 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 3e0fa0359a..3684d24132 100644 --- a/driver/src/test/java/org/neo4j/driver/integration/CredentialsIT.java +++ b/driver/src/test/java/org/neo4j/driver/integration/CredentialsIT.java @@ -64,7 +64,6 @@ void shouldBePossibleToChangePassword() throws Exception String tmpDataDir = Files.createTempDirectory( Paths.get( "target" ), "tmp" ).toAbsolutePath().toString().replace( "\\", "/" ); neo4j.restartDb( Neo4jSettings.TEST_SETTINGS - .updateWith( Neo4jSettings.AUTH_ENABLED, "true" ) .updateWith( Neo4jSettings.DATA_DIR, tmpDataDir ) ); AuthToken authToken = new InternalAuthToken( parameters( 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 33e4466a5b..e296fcc123 100644 --- a/driver/src/test/java/org/neo4j/driver/integration/RoutingDriverBoltKitTest.java +++ b/driver/src/test/java/org/neo4j/driver/integration/RoutingDriverBoltKitTest.java @@ -46,6 +46,7 @@ import org.neo4j.driver.exceptions.ServiceUnavailableException; import org.neo4j.driver.exceptions.SessionExpiredException; import org.neo4j.driver.exceptions.TransientException; +import org.neo4j.driver.internal.Bookmark; import org.neo4j.driver.internal.DriverFactory; import org.neo4j.driver.internal.cluster.RoutingSettings; import org.neo4j.driver.internal.retry.RetrySettings; @@ -73,6 +74,7 @@ import static org.mockito.Mockito.verify; import static org.mockito.Mockito.when; import static org.neo4j.driver.SessionConfig.builder; +import static org.neo4j.driver.internal.InternalBookmark.parse; import static org.neo4j.driver.util.StubServer.INSECURE_CONFIG; import static org.neo4j.driver.util.StubServer.insecureBuilder; @@ -520,7 +522,7 @@ void shouldSendInitialBookmark() throws Exception StubServer writer = StubServer.start( "write_tx_with_bookmarks.script", 9007 ); try ( Driver driver = GraphDatabase.driver( "neo4j://127.0.0.1:9001", INSECURE_CONFIG ); - Session session = driver.session( builder().withBookmarks( "OldBookmark" ).build() ) ) + Session session = driver.session( builder().withBookmarks( parse( "OldBookmark" ) ).build() ) ) { try ( Transaction tx = session.beginTransaction() ) { @@ -528,7 +530,7 @@ void shouldSendInitialBookmark() throws Exception tx.success(); } - assertEquals( "NewBookmark", session.lastBookmark() ); + assertEquals( parse( "NewBookmark" ), session.lastBookmark() ); } assertThat( router.exitStatus(), equalTo( 0 ) ); @@ -542,7 +544,7 @@ void shouldUseWriteSessionModeAndInitialBookmark() throws Exception StubServer writer = StubServer.start( "write_tx_with_bookmarks.script", 9008 ); try ( Driver driver = GraphDatabase.driver( "neo4j://127.0.0.1:9001", INSECURE_CONFIG ); - Session session = driver.session( builder().withDefaultAccessMode( AccessMode.WRITE ).withBookmarks( "OldBookmark" ).build() ) ) + Session session = driver.session( builder().withDefaultAccessMode( AccessMode.WRITE ).withBookmarks( parse( "OldBookmark" ) ).build() ) ) { try ( Transaction tx = session.beginTransaction() ) { @@ -550,7 +552,7 @@ void shouldUseWriteSessionModeAndInitialBookmark() throws Exception tx.success(); } - assertEquals( "NewBookmark", session.lastBookmark() ); + assertEquals( parse( "NewBookmark" ), session.lastBookmark() ); } assertThat( router.exitStatus(), equalTo( 0 ) ); @@ -564,7 +566,7 @@ void shouldUseReadSessionModeAndInitialBookmark() throws Exception StubServer writer = StubServer.start( "read_tx_with_bookmarks.script", 9005 ); try ( Driver driver = GraphDatabase.driver( "neo4j://127.0.0.1:9001", INSECURE_CONFIG ); - Session session = driver.session( builder().withDefaultAccessMode( AccessMode.READ ).withBookmarks( "OldBookmark" ).build() ) ) + Session session = driver.session( builder().withDefaultAccessMode( AccessMode.READ ).withBookmarks( parse( "OldBookmark" ) ).build() ) ) { try ( Transaction tx = session.beginTransaction() ) { @@ -575,7 +577,7 @@ void shouldUseReadSessionModeAndInitialBookmark() throws Exception tx.success(); } - assertEquals( "NewBookmark", session.lastBookmark() ); + assertEquals( parse( "NewBookmark" ), session.lastBookmark() ); } assertThat( router.exitStatus(), equalTo( 0 ) ); @@ -589,7 +591,7 @@ void shouldPassBookmarkFromTransactionToTransaction() throws Exception StubServer writer = StubServer.start( "write_read_tx_with_bookmarks.script", 9007 ); try ( Driver driver = GraphDatabase.driver( "neo4j://127.0.0.1:9001", INSECURE_CONFIG ); - Session session = driver.session( builder().withBookmarks( "BookmarkA" ).build() ) ) + Session session = driver.session( builder().withBookmarks( parse( "BookmarkA" ) ).build() ) ) { try ( Transaction tx = session.beginTransaction() ) { @@ -597,7 +599,7 @@ void shouldPassBookmarkFromTransactionToTransaction() throws Exception tx.success(); } - assertEquals( "BookmarkB", session.lastBookmark() ); + assertEquals( parse( "BookmarkB" ), session.lastBookmark() ); try ( Transaction tx = session.beginTransaction() ) { @@ -607,7 +609,7 @@ void shouldPassBookmarkFromTransactionToTransaction() throws Exception tx.success(); } - assertEquals( "BookmarkC", session.lastBookmark() ); + assertEquals( parse( "BookmarkC" ), session.lastBookmark() ); } assertThat( router.exitStatus(), equalTo( 0 ) ); @@ -980,11 +982,11 @@ void shouldSendMultipleBookmarks() throws Exception StubServer router = StubServer.start( "acquire_endpoints_v3.script", 9001 ); StubServer writer = StubServer.start( "multiple_bookmarks.script", 9007 ); - List bookmarks = + Bookmark bookmark = parse( asList( "neo4j:bookmark:v1:tx5", "neo4j:bookmark:v1:tx29", "neo4j:bookmark:v1:tx94", "neo4j:bookmark:v1:tx56", "neo4j:bookmark:v1:tx16", - "neo4j:bookmark:v1:tx68" ); + "neo4j:bookmark:v1:tx68" ) ); - try ( Driver driver = GraphDatabase.driver( "neo4j://localhost:9001", INSECURE_CONFIG ); Session session = driver.session( builder().withBookmarks( bookmarks ).build() ) ) + try ( Driver driver = GraphDatabase.driver( "neo4j://localhost:9001", INSECURE_CONFIG ); Session session = driver.session( builder().withBookmarks( bookmark ).build() ) ) { try ( Transaction tx = session.beginTransaction() ) { @@ -992,7 +994,7 @@ void shouldSendMultipleBookmarks() throws Exception tx.success(); } - assertEquals( "neo4j:bookmark:v1:tx95", session.lastBookmark() ); + assertEquals( parse( "neo4j:bookmark:v1:tx95" ), session.lastBookmark() ); } finally { diff --git a/driver/src/test/java/org/neo4j/driver/integration/RoutingDriverMultidatabaseBoltKitTest.java b/driver/src/test/java/org/neo4j/driver/integration/RoutingDriverMultidatabaseBoltKitTest.java index 5174a86b54..57b701342d 100644 --- a/driver/src/test/java/org/neo4j/driver/integration/RoutingDriverMultidatabaseBoltKitTest.java +++ b/driver/src/test/java/org/neo4j/driver/integration/RoutingDriverMultidatabaseBoltKitTest.java @@ -35,6 +35,7 @@ import org.neo4j.driver.exceptions.FatalDiscoveryException; import org.neo4j.driver.exceptions.ServiceUnavailableException; import org.neo4j.driver.integration.RoutingDriverBoltKitTest.PortBasedServerAddressComparator; +import org.neo4j.driver.internal.Bookmark; import org.neo4j.driver.net.ServerAddress; import org.neo4j.driver.net.ServerAddressResolver; import org.neo4j.driver.util.StubServer; @@ -46,6 +47,7 @@ import static org.junit.jupiter.api.Assertions.assertEquals; import static org.junit.jupiter.api.Assertions.assertThrows; import static org.neo4j.driver.SessionConfig.builder; +import static org.neo4j.driver.internal.InternalBookmark.parse; import static org.neo4j.driver.util.StubServer.INSECURE_CONFIG; import static org.neo4j.driver.util.StubServer.insecureBuilder; @@ -136,7 +138,7 @@ void shouldBeAbleToServeReachableDatabase() throws IOException, InterruptedExcep session.run( "MATCH (n) RETURN n.name" ); } ); - assertThat( error.getMessage(), containsString( "Could not perform discovery for database 'Unreachable'" ) ); + assertThat( error.getMessage(), containsString( "Could not perform discovery for database 'unreachable'" ) ); } try ( Session session = driver.session( builder().withDefaultAccessMode( AccessMode.READ ).withDatabase( "myDatabase" ).build() ) ) @@ -180,4 +182,61 @@ void shouldVerifyConnectivityOnDriverCreation() throws Throwable assertEquals( 0, router.exitStatus() ); } } + + @Test + void shouldPassSystemBookmarkWhenGettingRoutingTableForMultiDB() throws Throwable + { + Bookmark sysBookmark = parse( "sys:1234" ); + Bookmark fooBookmark = parse( "foo:5678" ); + StubServer router = StubServer.start( "acquire_endpoints_v4_with_bookmark.script", 9001 ); + StubServer readServer = StubServer.start( "read_server_v4_read_with_bookmark.script", 9005 ); + + URI uri = URI.create( "neo4j://127.0.0.1:9001" ); + try ( Driver driver = GraphDatabase.driver( uri, INSECURE_CONFIG ) ) + { + try ( Session session = driver.session( builder() + .withDatabase( "foo" ) + .withDefaultAccessMode( AccessMode.READ ) + .withBookmarks( sysBookmark, fooBookmark ) + .build() ) ) + { + List records = session.run( "MATCH (n) RETURN n.name" ).list(); + assertEquals( 3, records.size() ); + assertThat( session.lastBookmark(), equalTo( parse( "foo:6678" ) ) ); + } + } + finally + { + assertEquals( 0, readServer.exitStatus() ); + assertEquals( 0, router.exitStatus() ); + } + } + + @Test + void shouldIgnoreSystemBookmarkWhenGettingRoutingTable() throws Throwable + { + Bookmark sysBookmark = parse( "sys:1234" ); + Bookmark fooBookmark = parse( "foo:5678" ); + StubServer router = StubServer.start( "acquire_endpoints_v3.script", 9001 ); + StubServer readServer = StubServer.start( "read_server_v3_read_with_bookmark.script", 9005 ); + + URI uri = URI.create( "neo4j://127.0.0.1:9001" ); + try ( Driver driver = GraphDatabase.driver( uri, INSECURE_CONFIG ) ) + { + try ( Session session = driver.session( builder() + .withDefaultAccessMode( AccessMode.READ ) + .withBookmarks( sysBookmark, fooBookmark ) // you can still send, the real server will reject in session run of course. + .build() ) ) + { + List records = session.run( "MATCH (n) RETURN n.name" ).list(); + assertEquals( 3, records.size() ); + assertThat( session.lastBookmark(), equalTo( parse( "foo:6678" ) ) ); + } + } + finally + { + assertEquals( 0, readServer.exitStatus() ); + assertEquals( 0, router.exitStatus() ); + } + } } 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 cddc6567e9..ca3018afd9 100644 --- a/driver/src/test/java/org/neo4j/driver/integration/SessionBoltV3IT.java +++ b/driver/src/test/java/org/neo4j/driver/integration/SessionBoltV3IT.java @@ -38,6 +38,7 @@ import org.neo4j.driver.async.AsyncSession; import org.neo4j.driver.async.StatementResultCursor; import org.neo4j.driver.exceptions.TransientException; +import org.neo4j.driver.internal.Bookmark; import org.neo4j.driver.internal.cluster.RoutingSettings; import org.neo4j.driver.internal.messaging.Message; import org.neo4j.driver.internal.messaging.request.GoodbyeMessage; @@ -193,21 +194,21 @@ void shouldSetTransactionMetadataWithWriteTransactionFunction() void shouldUseBookmarksForAutoCommitTransactions() { Session session = driver.session(); - String initialBookmark = session.lastBookmark(); + Bookmark initialBookmark = session.lastBookmark(); session.run( "CREATE ()" ).consume(); - String bookmark1 = session.lastBookmark(); + Bookmark bookmark1 = session.lastBookmark(); assertNotNull( bookmark1 ); assertNotEquals( initialBookmark, bookmark1 ); session.run( "CREATE ()" ).consume(); - String bookmark2 = session.lastBookmark(); + Bookmark bookmark2 = session.lastBookmark(); assertNotNull( bookmark2 ); assertNotEquals( initialBookmark, bookmark2 ); assertNotEquals( bookmark1, bookmark2 ); session.run( "CREATE ()" ).consume(); - String bookmark3 = session.lastBookmark(); + Bookmark bookmark3 = session.lastBookmark(); assertNotNull( bookmark3 ); assertNotEquals( initialBookmark, bookmark3 ); assertNotEquals( bookmark1, bookmark3 ); @@ -218,19 +219,19 @@ void shouldUseBookmarksForAutoCommitTransactions() void shouldUseBookmarksForAutoCommitAndExplicitTransactions() { Session session = driver.session(); - String initialBookmark = session.lastBookmark(); + Bookmark initialBookmark = session.lastBookmark(); try ( Transaction tx = session.beginTransaction() ) { tx.run( "CREATE ()" ); tx.success(); } - String bookmark1 = session.lastBookmark(); + Bookmark bookmark1 = session.lastBookmark(); assertNotNull( bookmark1 ); assertNotEquals( initialBookmark, bookmark1 ); session.run( "CREATE ()" ).consume(); - String bookmark2 = session.lastBookmark(); + Bookmark bookmark2 = session.lastBookmark(); assertNotNull( bookmark2 ); assertNotEquals( initialBookmark, bookmark2 ); assertNotEquals( bookmark1, bookmark2 ); @@ -240,7 +241,7 @@ void shouldUseBookmarksForAutoCommitAndExplicitTransactions() tx.run( "CREATE ()" ); tx.success(); } - String bookmark3 = session.lastBookmark(); + Bookmark bookmark3 = session.lastBookmark(); assertNotNull( bookmark3 ); assertNotEquals( initialBookmark, bookmark3 ); assertNotEquals( bookmark1, bookmark3 ); @@ -251,21 +252,21 @@ void shouldUseBookmarksForAutoCommitAndExplicitTransactions() void shouldUseBookmarksForAutoCommitTransactionsAndTransactionFunctions() { Session session = driver.session(); - String initialBookmark = session.lastBookmark(); + Bookmark initialBookmark = session.lastBookmark(); session.writeTransaction( tx -> tx.run( "CREATE ()" ) ); - String bookmark1 = session.lastBookmark(); + Bookmark bookmark1 = session.lastBookmark(); assertNotNull( bookmark1 ); assertNotEquals( initialBookmark, bookmark1 ); session.run( "CREATE ()" ).consume(); - String bookmark2 = session.lastBookmark(); + Bookmark bookmark2 = session.lastBookmark(); assertNotNull( bookmark2 ); assertNotEquals( initialBookmark, bookmark2 ); assertNotEquals( bookmark1, bookmark2 ); session.writeTransaction( tx -> tx.run( "CREATE ()" ) ); - String bookmark3 = session.lastBookmark(); + Bookmark bookmark3 = session.lastBookmark(); assertNotNull( bookmark3 ); assertNotEquals( initialBookmark, bookmark3 ); assertNotEquals( bookmark1, bookmark3 ); 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 c10ccc8234..27047c1ead 100644 --- a/driver/src/test/java/org/neo4j/driver/integration/SessionIT.java +++ b/driver/src/test/java/org/neo4j/driver/integration/SessionIT.java @@ -86,10 +86,12 @@ import static org.mockito.Mockito.spy; import static org.mockito.Mockito.times; import static org.mockito.Mockito.verify; -import static org.neo4j.driver.Values.parameters; import static org.neo4j.driver.SessionConfig.builder; import static org.neo4j.driver.SessionConfig.forDatabase; +import static org.neo4j.driver.Values.parameters; import static org.neo4j.driver.internal.logging.DevNullLogging.DEV_NULL_LOGGING; +import static org.neo4j.driver.internal.util.BookmarkUtils.assertBookmarkContainsSingleValue; +import static org.neo4j.driver.internal.util.BookmarkUtils.assertBookmarkIsEmpty; import static org.neo4j.driver.internal.util.Matchers.arithmeticError; import static org.neo4j.driver.internal.util.Matchers.connectionAcquisitionTimeoutError; import static org.neo4j.driver.internal.util.Neo4jFeature.BOLT_V4; @@ -327,13 +329,13 @@ void readTxCommittedWithoutTxSuccess() try ( Driver driver = newDriverWithoutRetries(); Session session = driver.session() ) { - assertNull( session.lastBookmark() ); + assertBookmarkIsEmpty( session.lastBookmark() ); long answer = session.readTransaction( tx -> tx.run( "RETURN 42" ).single().get( 0 ).asLong() ); assertEquals( 42, answer ); // bookmark should be not-null after commit - assertNotNull( session.lastBookmark() ); + assertBookmarkContainsSingleValue( session.lastBookmark() ); } } @@ -363,7 +365,7 @@ void readTxRolledBackWithTxFailure() try ( Driver driver = newDriverWithoutRetries(); Session session = driver.session() ) { - assertNull( session.lastBookmark() ); + assertBookmarkIsEmpty( session.lastBookmark() ); long answer = session.readTransaction( tx -> { @@ -374,7 +376,7 @@ void readTxRolledBackWithTxFailure() assertEquals( 42, answer ); // bookmark should remain null after rollback - assertNull( session.lastBookmark() ); + assertBookmarkIsEmpty( session.lastBookmark() ); } } @@ -409,7 +411,7 @@ void readTxRolledBackWhenExceptionIsThrown() try ( Driver driver = newDriverWithoutRetries(); Session session = driver.session() ) { - assertNull( session.lastBookmark() ); + assertBookmarkIsEmpty( session.lastBookmark() ); assertThrows( IllegalStateException.class, () -> session.readTransaction( tx -> @@ -423,7 +425,7 @@ void readTxRolledBackWhenExceptionIsThrown() } ) ); // bookmark should remain null after rollback - assertNull( session.lastBookmark() ); + assertBookmarkIsEmpty( session.lastBookmark() ); } } @@ -456,7 +458,7 @@ void readTxRolledBackWhenMarkedBothSuccessAndFailure() try ( Driver driver = newDriverWithoutRetries(); Session session = driver.session() ) { - assertNull( session.lastBookmark() ); + assertBookmarkIsEmpty( session.lastBookmark() ); long answer = session.readTransaction( tx -> { @@ -468,7 +470,7 @@ void readTxRolledBackWhenMarkedBothSuccessAndFailure() assertEquals( 42, answer ); // bookmark should remain null after rollback - assertNull( session.lastBookmark() ); + assertBookmarkIsEmpty( session.lastBookmark() ); } } @@ -504,7 +506,7 @@ void readTxRolledBackWhenMarkedAsSuccessAndThrowsException() try ( Driver driver = newDriverWithoutRetries(); Session session = driver.session() ) { - assertNull( session.lastBookmark() ); + assertBookmarkIsEmpty( session.lastBookmark() ); assertThrows( IllegalStateException.class, () -> session.readTransaction( tx -> @@ -515,7 +517,7 @@ void readTxRolledBackWhenMarkedAsSuccessAndThrowsException() } ) ); // bookmark should remain null after rollback - assertNull( session.lastBookmark() ); + assertBookmarkIsEmpty( session.lastBookmark() ); } } 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 434eb2a40d..a31df4de5a 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 @@ -48,6 +48,7 @@ import org.neo4j.driver.exceptions.ServiceUnavailableException; import org.neo4j.driver.exceptions.SessionExpiredException; import org.neo4j.driver.exceptions.TransientException; +import org.neo4j.driver.internal.InternalBookmark; import org.neo4j.driver.internal.util.DisabledOnNeo4jWith; import org.neo4j.driver.internal.util.EnabledOnNeo4jWith; import org.neo4j.driver.internal.util.Futures; @@ -599,7 +600,8 @@ void shouldRunAfterRunFailureToAcquireConnection() @DisabledOnNeo4jWith( BOLT_V3 ) void shouldRunAfterBeginTxFailureOnBookmark() { - session = neo4j.driver().asyncSession( builder().withBookmarks( "Illegal Bookmark" ).build() ); + InternalBookmark illegalBookmark = InternalBookmark.parse( "Illegal Bookmark" ); + session = neo4j.driver().asyncSession( builder().withBookmarks( illegalBookmark ).build() ); assertThrows( ClientException.class, () -> await( session.beginTransactionAsync() ) ); @@ -611,7 +613,8 @@ void shouldRunAfterBeginTxFailureOnBookmark() @Test void shouldNotBeginTxAfterBeginTxFailureOnBookmark() { - session = neo4j.driver().asyncSession( builder().withBookmarks( "Illegal Bookmark" ).build() ); + InternalBookmark illegalBookmark = InternalBookmark.parse( "Illegal Bookmark" ); + session = neo4j.driver().asyncSession( builder().withBookmarks( illegalBookmark ).build() ); assertThrows( ClientException.class, () -> await( session.beginTransactionAsync() ) ); assertThrows( ClientException.class, () -> await( session.beginTransactionAsync() ) ); } @@ -620,7 +623,8 @@ void shouldNotBeginTxAfterBeginTxFailureOnBookmark() @EnabledOnNeo4jWith( BOLT_V3 ) void shouldNotRunAfterBeginTxFailureOnBookmark() { - session = neo4j.driver().asyncSession( builder().withBookmarks( "Illegal Bookmark" ).build() ); + InternalBookmark illegalBookmark = InternalBookmark.parse( "Illegal Bookmark" ); + session = neo4j.driver().asyncSession( builder().withBookmarks( illegalBookmark ).build() ); assertThrows( ClientException.class, () -> await( session.beginTransactionAsync() ) ); StatementResultCursor cursor = await( session.runAsync( "RETURN 'Hello!'" ) ); assertThrows( ClientException.class, () -> await( cursor.singleAsync() ) ); 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 c530f6ef66..d7c546b0dc 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 @@ -42,6 +42,7 @@ import org.neo4j.driver.exceptions.ClientException; import org.neo4j.driver.exceptions.NoSuchRecordException; import org.neo4j.driver.exceptions.ServiceUnavailableException; +import org.neo4j.driver.internal.Bookmark; import org.neo4j.driver.summary.ResultSummary; import org.neo4j.driver.summary.StatementType; import org.neo4j.driver.types.Node; @@ -63,6 +64,7 @@ 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.internal.InternalBookmark.parse; import static org.neo4j.driver.internal.util.Iterables.single; import static org.neo4j.driver.internal.util.Matchers.containsResultAvailableAfterAndResultConsumedAfter; import static org.neo4j.driver.internal.util.Matchers.syntaxError; @@ -91,12 +93,12 @@ void tearDown() @Test void shouldBePossibleToCommitEmptyTx() { - String bookmarkBefore = session.lastBookmark(); + Bookmark bookmarkBefore = session.lastBookmark(); AsyncTransaction tx = await( session.beginTransactionAsync() ); assertThat( await( tx.commitAsync() ), is( nullValue() ) ); - String bookmarkAfter = session.lastBookmark(); + Bookmark bookmarkAfter = session.lastBookmark(); assertNotNull( bookmarkAfter ); assertNotEquals( bookmarkBefore, bookmarkAfter ); @@ -105,12 +107,12 @@ void shouldBePossibleToCommitEmptyTx() @Test void shouldBePossibleToRollbackEmptyTx() { - String bookmarkBefore = session.lastBookmark(); + Bookmark bookmarkBefore = session.lastBookmark(); AsyncTransaction tx = await( session.beginTransactionAsync() ); assertThat( await( tx.rollbackAsync() ), is( nullValue() ) ); - String bookmarkAfter = session.lastBookmark(); + Bookmark bookmarkAfter = session.lastBookmark(); assertEquals( bookmarkBefore, bookmarkAfter ); } @@ -298,7 +300,7 @@ void shouldNotAllowNewStatementsAfterAnIncorrectStatement() @Test void shouldFailBoBeginTxWithInvalidBookmark() { - AsyncSession session = neo4j.driver().asyncSession( builder().withBookmarks( "InvalidBookmark" ).build() ); + AsyncSession session = neo4j.driver().asyncSession( builder().withBookmarks( parse( "InvalidBookmark" ) ).build() ); ClientException e = assertThrows( ClientException.class, () -> await( session.beginTransactionAsync() ) ); assertThat( e.getMessage(), containsString( "InvalidBookmark" ) ); @@ -654,13 +656,13 @@ void shouldFailToRunQueryAfterRollback() @Test void shouldUpdateSessionBookmarkAfterCommit() { - String bookmarkBefore = session.lastBookmark(); + Bookmark bookmarkBefore = session.lastBookmark(); await( session.beginTransactionAsync() .thenCompose( tx -> tx.runAsync( "CREATE (:MyNode)" ) .thenCompose( ignore -> tx.commitAsync() ) ) ); - String bookmarkAfter = session.lastBookmark(); + Bookmark bookmarkAfter = session.lastBookmark(); assertNotNull( bookmarkAfter ); assertNotEquals( bookmarkBefore, bookmarkAfter ); diff --git a/driver/src/test/java/org/neo4j/driver/integration/reactive/RxTransactionIT.java b/driver/src/test/java/org/neo4j/driver/integration/reactive/RxTransactionIT.java index c01c31472e..cc1173ac9d 100644 --- a/driver/src/test/java/org/neo4j/driver/integration/reactive/RxTransactionIT.java +++ b/driver/src/test/java/org/neo4j/driver/integration/reactive/RxTransactionIT.java @@ -43,6 +43,7 @@ import org.neo4j.driver.Value; import org.neo4j.driver.exceptions.ClientException; import org.neo4j.driver.exceptions.ServiceUnavailableException; +import org.neo4j.driver.internal.Bookmark; import org.neo4j.driver.internal.util.EnabledOnNeo4jWith; import org.neo4j.driver.reactive.RxStatementResult; import org.neo4j.driver.reactive.RxSession; @@ -69,6 +70,7 @@ 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.internal.InternalBookmark.parse; import static org.neo4j.driver.internal.util.Iterables.single; import static org.neo4j.driver.internal.util.Matchers.containsResultAvailableAfterAndResultConsumedAfter; import static org.neo4j.driver.internal.util.Matchers.syntaxError; @@ -93,12 +95,12 @@ void setUp() @Test void shouldBePossibleToCommitEmptyTx() { - String bookmarkBefore = session.lastBookmark(); + Bookmark bookmarkBefore = session.lastBookmark(); Mono commit = Mono.from( session.beginTransaction() ).flatMap( tx -> Mono.from( tx.commit() ) ); StepVerifier.create( commit ).verifyComplete(); - String bookmarkAfter = session.lastBookmark(); + Bookmark bookmarkAfter = session.lastBookmark(); assertNotNull( bookmarkAfter ); assertNotEquals( bookmarkBefore, bookmarkAfter ); @@ -107,12 +109,12 @@ void shouldBePossibleToCommitEmptyTx() @Test void shouldBePossibleToRollbackEmptyTx() { - String bookmarkBefore = session.lastBookmark(); + Bookmark bookmarkBefore = session.lastBookmark(); Mono rollback = Mono.from( session.beginTransaction() ).flatMap( tx -> Mono.from( tx.rollback() ) ); StepVerifier.create( rollback ).verifyComplete(); - String bookmarkAfter = session.lastBookmark(); + Bookmark bookmarkAfter = session.lastBookmark(); assertEquals( bookmarkBefore, bookmarkAfter ); } @@ -247,7 +249,7 @@ void shouldNotAllowNewStatementsAfterAnIncorrectStatement() @Test void shouldFailBoBeginTxWithInvalidBookmark() { - RxSession session = neo4j.driver().rxSession( builder().withBookmarks( "InvalidBookmark" ).build() ); + RxSession session = neo4j.driver().rxSession( builder().withBookmarks( parse( "InvalidBookmark" ) ).build() ); ClientException e = assertThrows( ClientException.class, () -> await( session.beginTransaction() ) ); assertThat( e.getMessage(), containsString( "InvalidBookmark" ) ); @@ -696,14 +698,14 @@ void shouldFailToRunQueryWhenTerminated() @Test void shouldUpdateSessionBookmarkAfterCommit() { - String bookmarkBefore = session.lastBookmark(); + Bookmark bookmarkBefore = session.lastBookmark(); await( Flux.usingWhen( session.beginTransaction(), tx -> tx.run( "CREATE (:MyNode)" ).records(), RxTransaction::commit, RxTransaction::rollback ) ); - String bookmarkAfter = session.lastBookmark(); + Bookmark bookmarkAfter = session.lastBookmark(); assertNotNull( bookmarkAfter ); assertNotEquals( bookmarkBefore, bookmarkAfter ); diff --git a/driver/src/test/java/org/neo4j/driver/internal/BookmarksTest.java b/driver/src/test/java/org/neo4j/driver/internal/BookmarksTest.java deleted file mode 100644 index 18a83052b6..0000000000 --- a/driver/src/test/java/org/neo4j/driver/internal/BookmarksTest.java +++ /dev/null @@ -1,178 +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; - -import org.junit.jupiter.api.Test; - -import java.util.Collections; -import java.util.HashMap; -import java.util.List; -import java.util.Map; - -import org.neo4j.driver.Value; - -import static java.util.Arrays.asList; -import static java.util.Collections.emptyList; -import static java.util.Collections.emptyMap; -import static java.util.Collections.singletonList; -import static org.junit.jupiter.api.Assertions.assertEquals; -import static org.junit.jupiter.api.Assertions.assertFalse; -import static org.junit.jupiter.api.Assertions.assertIterableEquals; -import static org.junit.jupiter.api.Assertions.assertNull; -import static org.junit.jupiter.api.Assertions.assertTrue; -import static org.neo4j.driver.Values.value; - -class BookmarksTest -{ - @Test - void isEmptyForEmptyBookmark() - { - Bookmarks bookmarks = Bookmarks.empty(); - assertTrue( bookmarks.isEmpty() ); - } - - @Test - void maxBookmarkAsStringForEmptyBookmark() - { - Bookmarks bookmarks = Bookmarks.empty(); - assertNull( bookmarks.maxBookmarkAsString() ); - } - - @Test - void asBeginTransactionParametersForEmptyBookmark() - { - Bookmarks bookmarks = Bookmarks.empty(); - assertEquals( emptyMap(), bookmarks.asBeginTransactionParameters() ); - } - - @Test - void isEmptyForNonEmptyBookmark() - { - Bookmarks bookmarks = Bookmarks.from( "SomeBookmark" ); - assertFalse( bookmarks.isEmpty() ); - } - - @Test - void maxBookmarkAsStringForNonEmptyBookmark() - { - Bookmarks bookmarks = Bookmarks.from( "SomeBookmark" ); - assertEquals( "SomeBookmark", bookmarks.maxBookmarkAsString() ); - } - - @Test - void asBeginTransactionParametersForNonEmptyBookmark() - { - Bookmarks bookmarks = Bookmarks.from( "SomeBookmark" ); - verifyParameters( bookmarks, "SomeBookmark", "SomeBookmark" ); - } - - @Test - void bookmarkFromString() - { - Bookmarks bookmarks = Bookmarks.from( "Cat" ); - assertEquals( "Cat", bookmarks.maxBookmarkAsString() ); - verifyParameters( bookmarks, "Cat", "Cat" ); - } - - @Test - void bookmarkFromNullString() - { - Bookmarks bookmarks = Bookmarks.from( (String) null ); - assertTrue( bookmarks.isEmpty() ); - } - - @Test - void bookmarkFromIterable() - { - Bookmarks bookmarks = Bookmarks.from( asList( - "neo4j:bookmark:v1:tx42", "neo4j:bookmark:v1:tx10", "neo4j:bookmark:v1:tx12" ) ); - assertEquals( "neo4j:bookmark:v1:tx42", bookmarks.maxBookmarkAsString() ); - verifyParameters( bookmarks, - "neo4j:bookmark:v1:tx42", - "neo4j:bookmark:v1:tx42", "neo4j:bookmark:v1:tx10", "neo4j:bookmark:v1:tx12" ); - } - - @Test - void bookmarkFromNullIterable() - { - Bookmarks bookmarks = Bookmarks.from( (Iterable) null ); - assertTrue( bookmarks.isEmpty() ); - } - - @Test - void bookmarkFromEmptyIterable() - { - Bookmarks bookmarks = Bookmarks.from( Collections.emptyList() ); - assertTrue( bookmarks.isEmpty() ); - } - - @Test - void asBeginTransactionParametersForBookmarkWithInvalidValue() - { - Bookmarks bookmarks = Bookmarks.from( asList( - "neo4j:bookmark:v1:tx1", "neo4j:bookmark:v1:txcat", "neo4j:bookmark:v1:tx3" ) ); - assertEquals( "neo4j:bookmark:v1:tx3", bookmarks.maxBookmarkAsString() ); - verifyParameters( bookmarks, - "neo4j:bookmark:v1:tx3", - "neo4j:bookmark:v1:tx1", "neo4j:bookmark:v1:txcat", "neo4j:bookmark:v1:tx3" ); - } - - @Test - void asBeginTransactionParametersForBookmarkWithEmptyStringValue() - { - Bookmarks bookmarks = Bookmarks.from( asList( "neo4j:bookmark:v1:tx9", "", "neo4j:bookmark:v1:tx3" ) ); - assertEquals( "neo4j:bookmark:v1:tx9", bookmarks.maxBookmarkAsString() ); - verifyParameters( bookmarks, - "neo4j:bookmark:v1:tx9", - "neo4j:bookmark:v1:tx9", "", "neo4j:bookmark:v1:tx3" ); - } - - @Test - void asBeginTransactionParametersForBookmarkWithNullValue() - { - Bookmarks bookmarks = Bookmarks.from( asList( "neo4j:bookmark:v1:tx41", null, "neo4j:bookmark:v1:tx42" ) ); - assertEquals( "neo4j:bookmark:v1:tx42", bookmarks.maxBookmarkAsString() ); - verifyParameters( bookmarks, - "neo4j:bookmark:v1:tx42", - asList( "neo4j:bookmark:v1:tx41", null, "neo4j:bookmark:v1:tx42" ) ); - } - - @Test - void shouldReturnAllBookmarks() - { - assertIterableEquals( emptyList(), Bookmarks.empty().values() ); - assertIterableEquals( singletonList( "neo4j:bookmark:v1:tx42" ), Bookmarks.from( "neo4j:bookmark:v1:tx42" ).values() ); - - List bookmarks = asList( "neo4j:bookmark:v1:tx1", "neo4j:bookmark:v1:tx2", "neo4j:bookmark:v1:tx3" ); - assertIterableEquals( bookmarks, Bookmarks.from( bookmarks ).values() ); - } - - private static void verifyParameters( Bookmarks bookmarks, String expectedMaxValue, String... expectedValues ) - { - verifyParameters( bookmarks, expectedMaxValue, asList( expectedValues ) ); - } - - private static void verifyParameters( Bookmarks bookmarks, String expectedMaxValue, List expectedValues ) - { - Map expectedParameters = new HashMap<>(); - expectedParameters.put( "bookmark", value( expectedMaxValue ) ); - expectedParameters.put( "bookmarks", value( expectedValues ) ); - assertEquals( expectedParameters, bookmarks.asBeginTransactionParameters() ); - } -} diff --git a/driver/src/test/java/org/neo4j/driver/internal/DefaultBookmarkHolderTest.java b/driver/src/test/java/org/neo4j/driver/internal/DefaultBookmarkHolderTest.java new file mode 100644 index 0000000000..be4915457e --- /dev/null +++ b/driver/src/test/java/org/neo4j/driver/internal/DefaultBookmarkHolderTest.java @@ -0,0 +1,88 @@ +/* + * 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; + +import org.junit.jupiter.api.Test; + +import static org.junit.jupiter.api.Assertions.assertEquals; + +class DefaultBookmarkHolderTest +{ + @Test + void shouldAllowToGetAndSetBookmarks() + { + BookmarkHolder bookmarkHolder = new DefaultBookmarkHolder(); + assertEquals( InternalBookmark.empty(), bookmarkHolder.getBookmark() ); + + bookmarkHolder.setBookmark( null ); + assertEquals( InternalBookmark.empty(), bookmarkHolder.getBookmark() ); + + bookmarkHolder.setBookmark( InternalBookmark.empty() ); + assertEquals( InternalBookmark.empty(), bookmarkHolder.getBookmark() ); + + InternalBookmark bookmark1 = InternalBookmark.parse( "neo4j:bookmark:v1:tx1" ); + bookmarkHolder.setBookmark( bookmark1 ); + assertEquals( bookmark1, bookmarkHolder.getBookmark() ); + + bookmarkHolder.setBookmark( null ); + assertEquals( bookmark1, bookmarkHolder.getBookmark() ); + + bookmarkHolder.setBookmark( InternalBookmark.empty() ); + assertEquals( bookmark1, bookmarkHolder.getBookmark() ); + + InternalBookmark bookmark2 = InternalBookmark.parse( "neo4j:bookmark:v1:tx2" ); + bookmarkHolder.setBookmark( bookmark2 ); + assertEquals( bookmark2, bookmarkHolder.getBookmark() ); + + InternalBookmark bookmark3 = InternalBookmark.parse( "neo4j:bookmark:v1:tx42" ); + bookmarkHolder.setBookmark( bookmark3 ); + assertEquals( bookmark3, bookmarkHolder.getBookmark() ); + } + + @Test + void bookmarkCanBeSet() + { + BookmarkHolder bookmarkHolder = new DefaultBookmarkHolder(); + InternalBookmark bookmark = InternalBookmark.parse( "neo4j:bookmark:v1:tx100" ); + + bookmarkHolder.setBookmark( bookmark ); + + assertEquals( bookmark, bookmarkHolder.getBookmark() ); + } + + @Test + void shouldNotOverwriteBookmarkWithNull() + { + InternalBookmark initBookmark = InternalBookmark.parse( "Cat" ); + BookmarkHolder bookmarkHolder = new DefaultBookmarkHolder( initBookmark ); + assertEquals( initBookmark, bookmarkHolder.getBookmark() ); + bookmarkHolder.setBookmark( null ); + assertEquals( initBookmark, bookmarkHolder.getBookmark() ); + } + + @Test + void shouldNotOverwriteBookmarkWithEmptyBookmark() + { + InternalBookmark initBookmark = InternalBookmark.parse( "Cat" ); + BookmarkHolder bookmarkHolder = new DefaultBookmarkHolder( initBookmark ); + assertEquals( initBookmark, bookmarkHolder.getBookmark() ); + bookmarkHolder.setBookmark( InternalBookmark.empty() ); + assertEquals( initBookmark, bookmarkHolder.getBookmark() ); + } +} diff --git a/driver/src/test/java/org/neo4j/driver/internal/DefaultBookmarksHolderTest.java b/driver/src/test/java/org/neo4j/driver/internal/DefaultBookmarksHolderTest.java deleted file mode 100644 index 714403031d..0000000000 --- a/driver/src/test/java/org/neo4j/driver/internal/DefaultBookmarksHolderTest.java +++ /dev/null @@ -1,96 +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; - -import org.junit.jupiter.api.Test; - -import static org.junit.jupiter.api.Assertions.assertEquals; - -class DefaultBookmarksHolderTest -{ - @Test - void shouldAllowToGetAndSetBookmarks() - { - BookmarksHolder bookmarksHolder = new DefaultBookmarksHolder(); - assertEquals( Bookmarks.empty(), bookmarksHolder.getBookmarks() ); - - bookmarksHolder.setBookmarks( null ); - assertEquals( Bookmarks.empty(), bookmarksHolder.getBookmarks() ); - - bookmarksHolder.setBookmarks( Bookmarks.empty() ); - assertEquals( Bookmarks.empty(), bookmarksHolder.getBookmarks() ); - - Bookmarks bookmarks1 = Bookmarks.from( "neo4j:bookmark:v1:tx1" ); - bookmarksHolder.setBookmarks( bookmarks1 ); - assertEquals( bookmarks1, bookmarksHolder.getBookmarks() ); - - bookmarksHolder.setBookmarks( null ); - assertEquals( bookmarks1, bookmarksHolder.getBookmarks() ); - - bookmarksHolder.setBookmarks( Bookmarks.empty() ); - assertEquals( bookmarks1, bookmarksHolder.getBookmarks() ); - - Bookmarks bookmarks2 = Bookmarks.from( "neo4j:bookmark:v1:tx2" ); - bookmarksHolder.setBookmarks( bookmarks2 ); - assertEquals( bookmarks2, bookmarksHolder.getBookmarks() ); - - Bookmarks bookmarks3 = Bookmarks.from( "neo4j:bookmark:v1:tx42" ); - bookmarksHolder.setBookmarks( bookmarks3 ); - assertEquals( bookmarks3, bookmarksHolder.getBookmarks() ); - } - - @Test - void bookmarkCanBeSet() - { - BookmarksHolder bookmarksHolder = new DefaultBookmarksHolder(); - Bookmarks bookmarks = Bookmarks.from( "neo4j:bookmark:v1:tx100" ); - - bookmarksHolder.setBookmarks( bookmarks ); - - assertEquals( bookmarks.maxBookmarkAsString(), bookmarksHolder.lastBookmark() ); - } - - @Test - void shouldNotOverwriteBookmarkWithNull() - { - BookmarksHolder bookmarksHolder = new DefaultBookmarksHolder( Bookmarks.from( "Cat" ) ); - assertEquals( "Cat", bookmarksHolder.lastBookmark() ); - bookmarksHolder.setBookmarks( null ); - assertEquals( "Cat", bookmarksHolder.lastBookmark() ); - } - - @Test - void shouldNotOverwriteBookmarkWithEmptyBookmark() - { - BookmarksHolder bookmarksHolder = new DefaultBookmarksHolder( Bookmarks.from( "Cat" ) ); - assertEquals( "Cat", bookmarksHolder.lastBookmark() ); - bookmarksHolder.setBookmarks( Bookmarks.empty() ); - assertEquals( "Cat", bookmarksHolder.lastBookmark() ); - } - - @Test - void setLastBookmark() - { - BookmarksHolder bookmarksHolder = new DefaultBookmarksHolder(); - - bookmarksHolder.setBookmarks( Bookmarks.from( "TheBookmark" ) ); - - assertEquals( "TheBookmark", bookmarksHolder.lastBookmark() ); - } -} \ No newline at end of file diff --git a/driver/src/test/java/org/neo4j/driver/internal/DirectConnectionProviderTest.java b/driver/src/test/java/org/neo4j/driver/internal/DirectConnectionProviderTest.java index 0a11a51ed9..95e2d8f68f 100644 --- a/driver/src/test/java/org/neo4j/driver/internal/DirectConnectionProviderTest.java +++ b/driver/src/test/java/org/neo4j/driver/internal/DirectConnectionProviderTest.java @@ -41,6 +41,8 @@ import static org.mockito.Mockito.when; import static org.neo4j.driver.AccessMode.READ; import static org.neo4j.driver.AccessMode.WRITE; +import static org.neo4j.driver.internal.cluster.RediscoveryUtils.contextWithDatabase; +import static org.neo4j.driver.internal.cluster.RediscoveryUtils.contextWithMode; import static org.neo4j.driver.internal.messaging.request.MultiDatabaseUtil.ABSENT_DB_NAME; import static org.neo4j.driver.util.TestUtil.await; @@ -56,11 +58,11 @@ void acquiresConnectionsFromThePool() ConnectionPool pool = poolMock( address, connection1, connection2 ); DirectConnectionProvider provider = new DirectConnectionProvider( address, pool ); - Connection acquired1 = await( provider.acquireConnection( ABSENT_DB_NAME, READ ) ); + Connection acquired1 = await( provider.acquireConnection( contextWithMode( READ ) ) ); assertThat( acquired1, instanceOf( DirectConnection.class ) ); assertSame( connection1, ((DirectConnection) acquired1).connection() ); - Connection acquired2 = await( provider.acquireConnection( ABSENT_DB_NAME, WRITE ) ); + Connection acquired2 = await( provider.acquireConnection( contextWithMode( WRITE ) ) ); assertThat( acquired2, instanceOf( DirectConnection.class ) ); assertSame( connection2, ((DirectConnection) acquired2).connection() ); } @@ -73,7 +75,7 @@ void returnsCorrectAccessMode( AccessMode mode ) ConnectionPool pool = poolMock( address, mock( Connection.class ) ); DirectConnectionProvider provider = new DirectConnectionProvider( address, pool ); - Connection acquired = await( provider.acquireConnection( ABSENT_DB_NAME, mode ) ); + Connection acquired = await( provider.acquireConnection( contextWithMode( mode ) ) ); assertEquals( mode, acquired.mode() ); } @@ -109,7 +111,7 @@ void shouldIgnoreDatabaseNameAndAccessModeWhenObtainConnectionFromPool() throws ConnectionPool pool = poolMock( address, connection ); DirectConnectionProvider provider = new DirectConnectionProvider( address, pool ); - Connection acquired1 = await( provider.acquireConnection( ABSENT_DB_NAME, READ ) ); + Connection acquired1 = await( provider.acquireConnection( contextWithMode( READ ) ) ); assertThat( acquired1, instanceOf( DirectConnection.class ) ); assertSame( connection, ((DirectConnection) acquired1).connection() ); @@ -125,7 +127,7 @@ void shouldObtainDatabaseNameOnConnection( String databaseName ) throws Throwabl ConnectionPool pool = poolMock( address, mock( Connection.class ) ); DirectConnectionProvider provider = new DirectConnectionProvider( address, pool ); - Connection acquired = await( provider.acquireConnection( databaseName, READ ) ); + Connection acquired = await( provider.acquireConnection( contextWithDatabase( databaseName ) ) ); assertEquals( databaseName, acquired.databaseName() ); } 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 cacea6a032..52a91174db 100644 --- a/driver/src/test/java/org/neo4j/driver/internal/DirectDriverBoltKitTest.java +++ b/driver/src/test/java/org/neo4j/driver/internal/DirectDriverBoltKitTest.java @@ -89,9 +89,9 @@ void shouldSendMultipleBookmarks() throws Exception { StubServer server = StubServer.start( "multiple_bookmarks.script", 9001 ); - List bookmarks = asList( "neo4j:bookmark:v1:tx5", "neo4j:bookmark:v1:tx29", + Bookmark bookmarks = InternalBookmark.parse( asList( "neo4j:bookmark:v1:tx5", "neo4j:bookmark:v1:tx29", "neo4j:bookmark:v1:tx94", "neo4j:bookmark:v1:tx56", "neo4j:bookmark:v1:tx16", - "neo4j:bookmark:v1:tx68" ); + "neo4j:bookmark:v1:tx68" ) ); try ( Driver driver = GraphDatabase.driver( "bolt://localhost:9001", INSECURE_CONFIG ); Session session = driver.session( builder().withBookmarks( bookmarks ).build() ) ) @@ -102,7 +102,7 @@ void shouldSendMultipleBookmarks() throws Exception tx.success(); } - assertEquals( "neo4j:bookmark:v1:tx95", session.lastBookmark() ); + assertEquals( InternalBookmark.parse( "neo4j:bookmark:v1:tx95" ), session.lastBookmark() ); } finally { @@ -240,8 +240,9 @@ void shouldThrowCorrectErrorOnRunFailure() throws Throwable { StubServer server = StubServer.start( "database_shutdown.script", 9001 ); + InternalBookmark bookmark = InternalBookmark.parse( "neo4j:bookmark:v1:tx0" ); try ( Driver driver = GraphDatabase.driver( "bolt://localhost:9001", INSECURE_CONFIG ); - Session session = driver.session( builder().withBookmarks( "neo4j:bookmark:v1:tx0" ).build() ); + Session session = driver.session( builder().withBookmarks( bookmark ).build() ); // has to enforce to flush BEGIN to have tx started. Transaction transaction = session.beginTransaction() ) { diff --git a/driver/src/test/java/org/neo4j/driver/internal/InternalBookmarkTest.java b/driver/src/test/java/org/neo4j/driver/internal/InternalBookmarkTest.java new file mode 100644 index 0000000000..74388ccb82 --- /dev/null +++ b/driver/src/test/java/org/neo4j/driver/internal/InternalBookmarkTest.java @@ -0,0 +1,203 @@ +/* + * 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; + +import org.junit.jupiter.api.Test; + +import java.io.ByteArrayInputStream; +import java.io.ByteArrayOutputStream; +import java.io.ObjectInputStream; +import java.io.ObjectOutputStream; +import java.util.Arrays; +import java.util.List; + +import org.neo4j.driver.internal.util.Iterables; + +import static java.util.Arrays.asList; +import static java.util.Collections.emptyList; +import static java.util.Collections.emptySet; +import static java.util.Collections.singletonList; +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.assertIterableEquals; +import static org.junit.jupiter.api.Assertions.assertTrue; +import static org.neo4j.driver.internal.InternalBookmark.parse; + +class InternalBookmarkTest +{ + @Test + void isEmptyForEmptyBookmark() + { + InternalBookmark bookmark = InternalBookmark.empty(); + assertTrue( bookmark.isEmpty() ); + assertEquals( emptySet(), bookmark.values() ); + } + + @Test + void shouldSetToEmptyForNullBookmark() throws Throwable + { + InternalBookmark bookmark = InternalBookmark.from( null ); + assertEquals( InternalBookmark.empty(), bookmark ); + } + + @Test + void shouldSetToEmptyForEmptyBookmarkIterator() throws Throwable + { + InternalBookmark bookmark = InternalBookmark.from( emptyList() ); + assertEquals( InternalBookmark.empty(), bookmark ); + } + + @Test + void shouldSetToEmptyForNullBookmarkList() throws Throwable + { + InternalBookmark bookmark = InternalBookmark.from( singletonList( null ) ); + assertEquals( InternalBookmark.empty(), bookmark ); + } + + @Test + void shouldIgnoreNullAndEmptyInBookmarkList() throws Throwable + { + InternalBookmark bookmark = InternalBookmark.from( Arrays.asList( InternalBookmark.empty(), null, null ) ); + assertEquals( InternalBookmark.empty(), bookmark ); + } + + @Test + void shouldReserveBookmarkValuesCorrectly() throws Throwable + { + Bookmark one = parse( "one" ); + Bookmark two = parse( "two" ); + Bookmark empty = InternalBookmark.empty(); + InternalBookmark bookmark = InternalBookmark.from( Arrays.asList( one, two, null, empty ) ); + assertThat( Iterables.asList( bookmark.values() ), equalTo( Arrays.asList( "one", "two" ) ) ); + } + + @Test + void isEmptyForNonEmptyBookmark() + { + InternalBookmark bookmark = InternalBookmark.parse( "SomeBookmark" ); + assertFalse( bookmark.isEmpty() ); + } + + @Test + void asBeginTransactionParametersForNonEmptyBookmark() + { + InternalBookmark bookmark = InternalBookmark.parse( "SomeBookmark" ); + verifyValues( bookmark, "SomeBookmark" ); + } + + @Test + void bookmarkFromString() + { + InternalBookmark bookmark = InternalBookmark.parse( "Cat" ); + assertEquals( singletonList( "Cat" ), bookmark.values() ); + verifyValues( bookmark, "Cat" ); + } + + @Test + void bookmarkFromNullString() + { + InternalBookmark bookmark = InternalBookmark.parse( (String) null ); + assertTrue( bookmark.isEmpty() ); + } + + @Test + void bookmarkFromIterable() + { + InternalBookmark bookmark = InternalBookmark.parse( asList( + "neo4j:bookmark:v1:tx42", "neo4j:bookmark:v1:tx10", "neo4j:bookmark:v1:tx12" ) ); + verifyValues( bookmark, "neo4j:bookmark:v1:tx42", "neo4j:bookmark:v1:tx10", "neo4j:bookmark:v1:tx12" ); + } + + @Test + void bookmarkFromNullIterable() + { + InternalBookmark bookmark = InternalBookmark.parse( (List) null ); + assertTrue( bookmark.isEmpty() ); + } + + @Test + void bookmarkFromEmptyIterable() + { + InternalBookmark bookmark = InternalBookmark.parse( emptyList() ); + assertTrue( bookmark.isEmpty() ); + } + + @Test + void asBeginTransactionParametersForBookmarkWithInvalidValue() + { + InternalBookmark bookmark = InternalBookmark.parse( asList( + "neo4j:bookmark:v1:tx1", "neo4j:bookmark:v1:txcat", "neo4j:bookmark:v1:tx3" ) ); + verifyValues( bookmark, "neo4j:bookmark:v1:tx1", "neo4j:bookmark:v1:txcat", "neo4j:bookmark:v1:tx3" ); + } + + @Test + void asBeginTransactionParametersForBookmarkWithEmptyStringValue() + { + InternalBookmark bookmark = InternalBookmark.parse( asList( "neo4j:bookmark:v1:tx9", "", "neo4j:bookmark:v1:tx3" ) ); + verifyValues( bookmark, "neo4j:bookmark:v1:tx9", "", "neo4j:bookmark:v1:tx3" ); + } + + @Test + void asBeginTransactionParametersForBookmarkWithNullValue() + { + InternalBookmark bookmark = InternalBookmark.parse( asList( "neo4j:bookmark:v1:tx41", null, "neo4j:bookmark:v1:tx42" ) ); + verifyValues( bookmark, asList( "neo4j:bookmark:v1:tx41", null, "neo4j:bookmark:v1:tx42" ) ); + } + + @Test + void shouldReturnAllBookmarks() + { + assertIterableEquals( emptyList(), InternalBookmark.empty().values() ); + assertIterableEquals( singletonList( "neo4j:bookmark:v1:tx42" ), InternalBookmark.parse( "neo4j:bookmark:v1:tx42" ).values() ); + + List bookmarks = asList( "neo4j:bookmark:v1:tx1", "neo4j:bookmark:v1:tx2", "neo4j:bookmark:v1:tx3" ); + assertIterableEquals( bookmarks, InternalBookmark.parse( bookmarks ).values() ); + } + + @Test + void objectShouldBeTheSameWhenSerializingAndDeserializing() throws Throwable + { + Bookmark bookmark = InternalBookmark.parse( Arrays.asList( "neo4j:1000", "neo4j:2000" ) ); + + ByteArrayOutputStream outputStream = new ByteArrayOutputStream(); + ObjectOutputStream objectOutputStream = new ObjectOutputStream( outputStream ); + objectOutputStream.writeObject( bookmark ); + objectOutputStream.flush(); + objectOutputStream.close(); + + ByteArrayInputStream inputStream = new ByteArrayInputStream( outputStream.toByteArray() ); + ObjectInputStream objectInputStream = new ObjectInputStream( inputStream ); + Bookmark readBookmark = (InternalBookmark) objectInputStream.readObject(); + objectInputStream.close(); + + assertEquals( bookmark, readBookmark ); + } + + private static void verifyValues( InternalBookmark bookmark, String... expectedValues ) + { + verifyValues( bookmark, asList( expectedValues ) ); + } + + private static void verifyValues( InternalBookmark bookmark, List expectedValues ) + { + assertIterableEquals( expectedValues, bookmark.values() ); + } +} diff --git a/driver/src/test/java/org/neo4j/driver/internal/InternalSessionTest.java b/driver/src/test/java/org/neo4j/driver/internal/InternalSessionTest.java deleted file mode 100644 index 057154fe36..0000000000 --- a/driver/src/test/java/org/neo4j/driver/internal/InternalSessionTest.java +++ /dev/null @@ -1,807 +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; - -import org.junit.jupiter.api.BeforeEach; -import org.junit.jupiter.api.Test; -import org.junit.jupiter.params.ParameterizedTest; -import org.junit.jupiter.params.provider.MethodSource; -import org.mockito.InOrder; - -import java.util.function.Function; -import java.util.function.Supplier; -import java.util.stream.Stream; - -import org.neo4j.driver.AccessMode; -import org.neo4j.driver.Session; -import org.neo4j.driver.Statement; -import org.neo4j.driver.StatementResult; -import org.neo4j.driver.Transaction; -import org.neo4j.driver.TransactionConfig; -import org.neo4j.driver.TransactionWork; -import org.neo4j.driver.Value; -import org.neo4j.driver.exceptions.ClientException; -import org.neo4j.driver.exceptions.ServiceUnavailableException; -import org.neo4j.driver.exceptions.SessionExpiredException; -import org.neo4j.driver.internal.messaging.BoltProtocol; -import org.neo4j.driver.internal.messaging.request.CommitMessage; -import org.neo4j.driver.internal.messaging.request.PullMessage; -import org.neo4j.driver.internal.messaging.request.RunWithMetadataMessage; -import org.neo4j.driver.internal.messaging.v4.BoltProtocolV4; -import org.neo4j.driver.internal.retry.RetryLogic; -import org.neo4j.driver.internal.spi.Connection; -import org.neo4j.driver.internal.spi.ConnectionProvider; -import org.neo4j.driver.internal.util.FixedRetryLogic; -import org.neo4j.driver.internal.value.IntegerValue; -import org.neo4j.driver.summary.ResultSummary; - -import static java.util.Collections.singletonList; -import static java.util.Collections.singletonMap; -import static java.util.concurrent.CompletableFuture.completedFuture; -import static org.hamcrest.CoreMatchers.containsString; -import static org.hamcrest.CoreMatchers.equalTo; -import static org.hamcrest.CoreMatchers.instanceOf; -import static org.hamcrest.junit.MatcherAssert.assertThat; -import static org.junit.jupiter.api.Assertions.assertEquals; -import static org.junit.jupiter.api.Assertions.assertNotNull; -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.mockito.ArgumentMatchers.any; -import static org.mockito.ArgumentMatchers.eq; -import static org.mockito.Mockito.atLeastOnce; -import static org.mockito.Mockito.doReturn; -import static org.mockito.Mockito.inOrder; -import static org.mockito.Mockito.mock; -import static org.mockito.Mockito.never; -import static org.mockito.Mockito.spy; -import static org.mockito.Mockito.times; -import static org.mockito.Mockito.verify; -import static org.mockito.Mockito.when; -import static org.neo4j.driver.AccessMode.READ; -import static org.neo4j.driver.AccessMode.WRITE; -import static org.neo4j.driver.TransactionConfig.empty; -import static org.neo4j.driver.Values.parameters; -import static org.neo4j.driver.internal.messaging.request.MultiDatabaseUtil.ABSENT_DB_NAME; -import static org.neo4j.driver.internal.util.Futures.failedFuture; -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.setupFailingCommit; -import static org.neo4j.driver.util.TestUtil.setupSuccessfulRunAndPull; -import static org.neo4j.driver.util.TestUtil.verifyBeginTx; -import static org.neo4j.driver.util.TestUtil.verifyCommitTx; -import static org.neo4j.driver.util.TestUtil.verifyRollbackTx; -import static org.neo4j.driver.util.TestUtil.verifyRunAndPull; - -class InternalSessionTest -{ - private Connection connection; - private ConnectionProvider connectionProvider; - private InternalSession session; - - @BeforeEach - void setUp() - { - connection = connectionMock( BoltProtocolV4.INSTANCE ); - connectionProvider = mock( ConnectionProvider.class ); - when( connectionProvider.acquireConnection( any( String.class ), any( AccessMode.class ) ) ) - .thenReturn( completedFuture( connection ) ); - session = new InternalSession( newSession( connectionProvider ) ); - } - - private static Stream> allSessionRunMethods() - { - return Stream.of( - session -> session.run( "RETURN 1" ), - session -> session.run( "RETURN $x", parameters( "x", 1 ) ), - session -> session.run( "RETURN $x", singletonMap( "x", 1 ) ), - session -> session.run( "RETURN $x", - new InternalRecord( singletonList( "x" ), new Value[]{new IntegerValue( 1 )} ) ), - session -> session.run( new Statement( "RETURN $x", parameters( "x", 1 ) ) ), - session -> session.run( new Statement( "RETURN $x", parameters( "x", 1 ) ), empty() ), - session -> session.run( "RETURN $x", singletonMap( "x", 1 ), empty() ), - session -> session.run( "RETURN 1", empty() ) - ); - } - - private static Stream> allBeginTxMethods() - { - return Stream.of( - session -> session.beginTransaction(), - session -> session.beginTransaction( TransactionConfig.empty() ) - ); - } - - private static Stream> allRunTxMethods() - { - return Stream.of( - session -> session.readTransaction( tx -> "a" ), - session -> session.writeTransaction( tx -> "a" ), - session -> session.readTransaction( tx -> "a", empty() ), - session -> session.writeTransaction( tx -> "a", empty() ) - ); - } - - @ParameterizedTest - @MethodSource( "allSessionRunMethods" ) - void shouldFlushOnRun( Function runReturnOne ) throws Throwable - { - setupSuccessfulRunAndPull( connection ); - - StatementResult result = runReturnOne.apply( session ); - ResultSummary summary = result.summary(); - - verifyRunAndPull( connection, summary.statement().text() ); - } - - @ParameterizedTest - @MethodSource( "allBeginTxMethods" ) - void shouldBeginTx( Function beginTx ) throws Throwable - { - Transaction tx = beginTx.apply( session ); - - verifyBeginTx( connection ); - assertNotNull( tx ); - } - - @ParameterizedTest - @MethodSource( "allRunTxMethods" ) - void runTxShouldBeginTxAndCloseTx( Function runTx ) throws Throwable - { - String string = runTx.apply( session ); - - verifyBeginTx( connection ); - verify( connection ).writeAndFlush( any( CommitMessage.class ), any() ); - verify( connection ).release(); - assertThat( string, equalTo( "a" ) ); - } - - @Test - void shouldNotAllowNewTxWhileOneIsRunning() - { - // Given - session.beginTransaction(); - - // Expect - assertThrows( ClientException.class, session::beginTransaction ); - } - - @Test - void shouldBeAbleToOpenTxAfterPreviousIsClosed() - { - // Given - session.beginTransaction().close(); - - // When - Transaction tx = session.beginTransaction(); - - // Then we should've gotten a transaction object back - assertNotNull( tx ); - } - - @Test - void shouldNotBeAbleToUseSessionWhileOngoingTransaction() - { - // Given - session.beginTransaction(); - - // Expect - assertThrows( ClientException.class, () -> session.run( "RETURN 1" ) ); - } - - @Test - void shouldBeAbleToUseSessionAgainWhenTransactionIsClosed() - { - // Given - session.beginTransaction().close(); - - // When - session.run( "RETURN 1" ); - - // Then - verifyRunAndPull( connection, "RETURN 1" ); - } - - @Test - void shouldNotCloseAlreadyClosedSession() - { - Transaction tx = session.beginTransaction(); - - session.close(); - session.close(); - session.close(); - - verifyRollbackTx( connection ); - } - - @Test - void runThrowsWhenSessionIsClosed() - { - session.close(); - - Exception e = assertThrows( Exception.class, () -> session.run( "CREATE ()" ) ); - assertThat( e, instanceOf( ClientException.class ) ); - assertThat( e.getMessage(), containsString( "session is already closed" ) ); - } - - @Test - void acquiresNewConnectionForRun() - { - session.run( "RETURN 1" ); - - verify( connectionProvider ).acquireConnection( any( String.class ), any( AccessMode.class ) ); - } - - @Test - void releasesOpenConnectionUsedForRunWhenSessionIsClosed() - { - String query = "RETURN 1"; - setupSuccessfulRunAndPull( connection, query ); - - session.run( query ); - session.close(); - - InOrder inOrder = inOrder( connection ); - inOrder.verify( connection ).writeAndFlush( any( RunWithMetadataMessage.class ), any(), any( PullMessage.class ), any() ); - inOrder.verify( connection, atLeastOnce() ).release(); - } - - @SuppressWarnings( "deprecation" ) - @Test - void resetDoesNothingWhenNoTransactionAndNoConnection() - { - session.reset(); - - verify( connectionProvider, never() ).acquireConnection( any( String.class ), any( AccessMode.class ) ); - } - - @Test - void closeWithoutConnection() - { - session.close(); - - verify( connectionProvider, never() ).acquireConnection( any( String.class ), any( AccessMode.class ) ); - } - - @Test - void acquiresNewConnectionForBeginTx() - { - Transaction tx = session.beginTransaction(); - - assertNotNull( tx ); - verify( connectionProvider ).acquireConnection( any( String.class ), any( AccessMode.class ) ); - } - - @Test - void updatesBookmarkWhenTxIsClosed() - { - Bookmarks bookmarkAfterCommit = Bookmarks.from( "TheBookmark" ); - - BoltProtocol protocol = spy( BoltProtocolV4.INSTANCE ); - doReturn( completedFuture( bookmarkAfterCommit ) ).when( protocol ).commitTransaction( any( Connection.class ) ); - - when( connection.protocol() ).thenReturn( protocol ); - - Transaction tx = session.beginTransaction(); - assertNull( session.lastBookmark() ); - - tx.success(); - tx.close(); - assertEquals( "TheBookmark", session.lastBookmark() ); - } - - @Test - void releasesConnectionWhenTxIsClosed() - { - String query = "RETURN 42"; - setupSuccessfulRunAndPull( connection, query ); - - Transaction tx = session.beginTransaction(); - tx.run( query ); - - verify( connectionProvider ).acquireConnection( any( String.class ), any( AccessMode.class ) ); - verifyRunAndPull( connection, query ); - - tx.close(); - verify( connection ).release(); - } - - @Test - void bookmarkIsPropagatedFromSession() - { - Bookmarks bookmarks = Bookmarks.from( "Bookmarks" ); - Session session = new InternalSession( newSession( connectionProvider, bookmarks ) ); - - Transaction tx = session.beginTransaction(); - assertNotNull( tx ); - verifyBeginTx( connection, bookmarks ); - } - - @Test - void bookmarkIsPropagatedBetweenTransactions() - { - Bookmarks bookmarks1 = Bookmarks.from( "Bookmark1" ); - Bookmarks bookmarks2 = Bookmarks.from( "Bookmark2" ); - - Session session = new InternalSession( newSession( connectionProvider) ); - - BoltProtocol protocol = spy( BoltProtocolV4.INSTANCE ); - doReturn( completedFuture( bookmarks1 ), completedFuture( bookmarks2 ) ).when( protocol ).commitTransaction( any( Connection.class ) ); - - when( connection.protocol() ).thenReturn( protocol ); - - try ( Transaction tx = session.beginTransaction() ) - { - tx.success(); - } - assertEquals( bookmarks1, Bookmarks.from( session.lastBookmark() ) ); - - try ( Transaction tx = session.beginTransaction() ) - { - verifyBeginTx( connection, bookmarks1 ); - tx.success(); - } - assertEquals( bookmarks2, Bookmarks.from( session.lastBookmark() ) ); - } - - @Test - void accessModeUsedToAcquireConnections() - { - Session session1 = new InternalSession( newSession( connectionProvider, READ ) ); - session1.beginTransaction(); - verify( connectionProvider ).acquireConnection( ABSENT_DB_NAME, READ ); - - Session session2 = new InternalSession( newSession( connectionProvider, WRITE ) ); - session2.beginTransaction(); - verify( connectionProvider ).acquireConnection( ABSENT_DB_NAME, WRITE ); - } - - @Test - void testPassingNoBookmarkShouldRetainBookmark() - { - Session session = new InternalSession( newSession( connectionProvider, Bookmarks.from( "X" ) ) ); - session.beginTransaction(); - assertThat( session.lastBookmark(), equalTo( "X" ) ); - } - - @Test - void acquiresReadConnectionForReadTxInReadSession() - { - testConnectionAcquisition( READ, READ ); - } - - @Test - void acquiresWriteConnectionForWriteTxInReadSession() - { - testConnectionAcquisition( READ, WRITE ); - } - - @Test - void acquiresReadConnectionForReadTxInWriteSession() - { - testConnectionAcquisition( WRITE, READ ); - } - - @Test - void acquiresWriteConnectionForWriteTxInWriteSession() - { - testConnectionAcquisition( WRITE, WRITE ); - } - - @Test - void commitsReadTxWhenMarkedSuccessful() - { - testTxCommitOrRollback( READ, true ); - } - - @Test - void commitsWriteTxWhenMarkedSuccessful() - { - testTxCommitOrRollback( WRITE, true ); - } - - @Test - void rollsBackReadTxWhenMarkedSuccessful() - { - testTxCommitOrRollback( READ, false ); - } - - @Test - void rollsBackWriteTxWhenMarkedSuccessful() - { - testTxCommitOrRollback( READ, true ); - } - - @Test - void rollsBackReadTxWhenFunctionThrows() - { - testTxRollbackWhenThrows( READ ); - } - - @Test - void rollsBackWriteTxWhenFunctionThrows() - { - testTxRollbackWhenThrows( WRITE ); - } - - @Test - void readTxRetriedUntilSuccessWhenFunctionThrows() - { - testTxIsRetriedUntilSuccessWhenFunctionThrows( READ ); - } - - @Test - void writeTxRetriedUntilSuccessWhenFunctionThrows() - { - testTxIsRetriedUntilSuccessWhenFunctionThrows( WRITE ); - } - - @Test - void readTxRetriedUntilSuccessWhenTxCloseThrows() - { - testTxIsRetriedUntilSuccessWhenCommitThrows( READ ); - } - - @Test - void writeTxRetriedUntilSuccessWhenTxCloseThrows() - { - testTxIsRetriedUntilSuccessWhenCommitThrows( WRITE ); - } - - @Test - void readTxRetriedUntilFailureWhenFunctionThrows() - { - testTxIsRetriedUntilFailureWhenFunctionThrows( READ ); - } - - @Test - void writeTxRetriedUntilFailureWhenFunctionThrows() - { - testTxIsRetriedUntilFailureWhenFunctionThrows( WRITE ); - } - - @Test - void readTxRetriedUntilFailureWhenTxCloseThrows() - { - testTxIsRetriedUntilFailureWhenCommitFails( READ ); - } - - @Test - void writeTxRetriedUntilFailureWhenTxCloseThrows() - { - testTxIsRetriedUntilFailureWhenCommitFails( WRITE ); - } - - @Test - void connectionShouldBeResetAfterSessionReset() - { - session.run( "RETURN 1" ); - - verify( connection, never() ).reset(); - verify( connection, never() ).release(); - - session.reset(); - verify( connection ).reset(); - verify( connection, never() ).release(); - } - - @Test - void shouldHaveNullLastBookmarkInitially() - { - assertNull( session.lastBookmark() ); - } - - @Test - void shouldDoNothingWhenClosingWithoutAcquiredConnection() - { - RuntimeException error = new RuntimeException( "Hi" ); - when( connectionProvider.acquireConnection( any( String.class ), any( AccessMode.class ) ) ).thenReturn( failedFuture( error ) ); - - Exception e = assertThrows( Exception.class, () -> session.run( "RETURN 1" ) ); - assertEquals( error, e ); - - session.close(); - } - - @Test - void shouldRunAfterRunFailureToAcquireConnection() - { - RuntimeException error = new RuntimeException( "Hi" ); - when( connectionProvider.acquireConnection( any( String.class ), any( AccessMode.class ) ) ) - .thenReturn( failedFuture( error ) ).thenReturn( completedFuture( connection ) ); - - Exception e = assertThrows( Exception.class, () -> session.run( "RETURN 1" ) ); - assertEquals( error, e ); - - session.run( "RETURN 2" ); - - verify( connectionProvider, times( 2 ) ).acquireConnection( any( String.class ), any( AccessMode.class ) ); - verifyRunAndPull( connection, "RETURN 2" ); - } - - @Test - void shouldRunAfterBeginTxFailureOnBookmark() - { - RuntimeException error = new RuntimeException( "Hi" ); - Connection connection1 = connectionMock( BoltProtocolV4.INSTANCE ); - setupFailingBegin( connection1, error ); - Connection connection2 = connectionMock( BoltProtocolV4.INSTANCE ); - - when( connectionProvider.acquireConnection( any( String.class ), any( AccessMode.class ) ) ) - .thenReturn( completedFuture( connection1 ) ).thenReturn( completedFuture( connection2 ) ); - - Bookmarks bookmarks = Bookmarks.from( "neo4j:bookmark:v1:tx42" ); - Session session = new InternalSession( newSession( connectionProvider, bookmarks ) ); - - Exception e = assertThrows( Exception.class, session::beginTransaction ); - assertEquals( error, e ); - - session.run( "RETURN 2" ); - - verify( connectionProvider, times( 2 ) ).acquireConnection( any( String.class ), any( AccessMode.class ) ); - verifyBeginTx( connection1, bookmarks ); - verifyRunAndPull( connection2, "RETURN 2" ); - } - - @Test - void shouldBeginTxAfterBeginTxFailureOnBookmark() - { - RuntimeException error = new RuntimeException( "Hi" ); - Connection connection1 = connectionMock( BoltProtocolV4.INSTANCE ); - setupFailingBegin( connection1, error ); - Connection connection2 = connectionMock( BoltProtocolV4.INSTANCE ); - - when( connectionProvider.acquireConnection( any( String.class ), any( AccessMode.class ) ) ) - .thenReturn( completedFuture( connection1 ) ).thenReturn( completedFuture( connection2 ) ); - - Bookmarks bookmarks = Bookmarks.from( "neo4j:bookmark:v1:tx42" ); - Session session = new InternalSession( newSession( connectionProvider, bookmarks ) ); - - Exception e = assertThrows( Exception.class, session::beginTransaction ); - assertEquals( error, e ); - - session.beginTransaction(); - - verify( connectionProvider, times( 2 ) ).acquireConnection( any( String.class ), any( AccessMode.class ) ); - verifyBeginTx( connection1, bookmarks ); - verifyBeginTx( connection2, bookmarks ); - } - - @Test - void shouldBeginTxAfterRunFailureToAcquireConnection() - { - RuntimeException error = new RuntimeException( "Hi" ); - when( connectionProvider.acquireConnection( any( String.class ), any( AccessMode.class ) ) ) - .thenReturn( failedFuture( error ) ).thenReturn( completedFuture( connection ) ); - - Exception e = assertThrows( Exception.class, () -> session.run( "RETURN 1" ) ); - assertEquals( error, e ); - - session.beginTransaction(); - - verify( connectionProvider, times( 2 ) ).acquireConnection( any( String.class ), any( AccessMode.class ) ); - verifyBeginTx( connection ); - } - - @Test - void shouldMarkTransactionAsTerminatedAndThenResetConnectionOnReset() - { - Transaction tx = session.beginTransaction(); - - assertTrue( tx.isOpen() ); - verify( connection, never() ).reset(); - - session.reset(); - - verify( connection ).reset(); - } - - private void testConnectionAcquisition( AccessMode sessionMode, AccessMode transactionMode ) - { - Session session = new InternalSession( newSession( connectionProvider, sessionMode ) ); - - TxWork work = new TxWork( 42 ); - - int result = executeTransaction( session, transactionMode, work ); - - verify( connectionProvider ).acquireConnection( any( String.class ), eq( transactionMode ) ); - verifyBeginTx( connection ); - verifyCommitTx( connection ); - assertEquals( 42, result ); - } - - private void testTxCommitOrRollback( AccessMode transactionMode, final boolean commit ) - { - Session session = new InternalSession( newSession( connectionProvider, WRITE ) ); - - TransactionWork work = tx -> - { - if ( !commit ) - { - tx.failure(); - } - return 4242; - }; - - int result = executeTransaction( session, transactionMode, work ); - - verify( connectionProvider ).acquireConnection( any( String.class ), eq( transactionMode ) ); - verifyBeginTx( connection ); - if ( commit ) - { - verifyCommitTx( connection ); - verifyRollbackTx( connection, never() ); - } - else - { - verifyRollbackTx( connection ); - verifyCommitTx( connection, never() ); - } - assertEquals( 4242, result ); - } - - private void testTxRollbackWhenThrows( AccessMode transactionMode ) - { - final RuntimeException error = new IllegalStateException( "Oh!" ); - TransactionWork work = tx -> - { - throw error; - }; - - Exception e = assertThrows( Exception.class, () -> executeTransaction( session, transactionMode, work ) ); - assertEquals( error, e ); - - verify( connectionProvider ).acquireConnection( any( String.class ), eq( transactionMode ) ); - verifyBeginTx( connection ); - verifyRollbackTx( connection ); - } - - private void testTxIsRetriedUntilSuccessWhenFunctionThrows( AccessMode mode ) - { - int failures = 12; - int retries = failures + 1; - - RetryLogic retryLogic = new FixedRetryLogic( retries ); - Session session = new InternalSession( newSession( connectionProvider, retryLogic ) ); - - TxWork work = spy( new TxWork( 42, failures, new SessionExpiredException( "" ) ) ); - int answer = executeTransaction( session, mode, work ); - - assertEquals( 42, answer ); - verifyInvocationCount( work, failures + 1 ); - verifyCommitTx( connection ); - verifyRollbackTx( connection, times( failures ) ); - } - - private void testTxIsRetriedUntilSuccessWhenCommitThrows( AccessMode mode ) - { - int failures = 13; - int retries = failures + 1; - - RetryLogic retryLogic = new FixedRetryLogic( retries ); - setupFailingCommit( connection, failures ); - Session session = new InternalSession( newSession( connectionProvider, retryLogic ) ); - - TxWork work = spy( new TxWork( 43 ) ); - int answer = executeTransaction( session, mode, work ); - - assertEquals( 43, answer ); - verifyInvocationCount( work, failures + 1 ); - verifyCommitTx( connection, times( retries ) ); - } - - private void testTxIsRetriedUntilFailureWhenFunctionThrows( AccessMode mode ) - { - int failures = 14; - int retries = failures - 1; - - RetryLogic retryLogic = new FixedRetryLogic( retries ); - Session session = new InternalSession( newSession( connectionProvider, retryLogic ) ); - - TxWork work = spy( new TxWork( 42, failures, new SessionExpiredException( "Oh!" ) ) ); - - Exception e = assertThrows( Exception.class, () -> executeTransaction( session, mode, work ) ); - - assertThat( e, instanceOf( SessionExpiredException.class ) ); - assertEquals( "Oh!", e.getMessage() ); - verifyInvocationCount( work, failures ); - verifyCommitTx( connection, never() ); - verifyRollbackTx( connection, times( failures ) ); - } - - private void testTxIsRetriedUntilFailureWhenCommitFails( AccessMode mode ) - { - int failures = 17; - int retries = failures - 1; - - RetryLogic retryLogic = new FixedRetryLogic( retries ); - setupFailingCommit( connection, failures ); - Session session = new InternalSession( newSession( connectionProvider, retryLogic ) ); - - TxWork work = spy( new TxWork( 42 ) ); - - Exception e = assertThrows( Exception.class, () -> executeTransaction( session, mode, work ) ); - - assertThat( e, instanceOf( ServiceUnavailableException.class ) ); - verifyInvocationCount( work, failures ); - verifyCommitTx( connection, times( failures ) ); - } - - private static T executeTransaction( Session session, AccessMode mode, TransactionWork work ) - { - if ( mode == READ ) - { - return session.readTransaction( work ); - } - else if ( mode == WRITE ) - { - return session.writeTransaction( work ); - } - else - { - throw new IllegalArgumentException( "Unknown mode " + mode ); - } - } - - private static void verifyInvocationCount( TransactionWork workSpy, int expectedInvocationCount ) - { - verify( workSpy, times( expectedInvocationCount ) ).execute( any( Transaction.class ) ); - } - - private static class TxWork implements TransactionWork - { - final int result; - final int timesToThrow; - final Supplier errorSupplier; - - int invoked; - - @SuppressWarnings( "unchecked" ) - TxWork( int result ) - { - this( result, 0, (Supplier) null ); - } - - TxWork( int result, int timesToThrow, final RuntimeException error ) - { - this.result = result; - this.timesToThrow = timesToThrow; - this.errorSupplier = () -> error; - } - - TxWork( int result, int timesToThrow, Supplier errorSupplier ) - { - this.result = result; - this.timesToThrow = timesToThrow; - this.errorSupplier = errorSupplier; - } - - @Override - public Integer execute( Transaction tx ) - { - if ( timesToThrow > 0 && invoked++ < timesToThrow ) - { - throw errorSupplier.get(); - } - tx.success(); - return result; - } - } -} 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 086ef8a366..ad44bdada3 100644 --- a/driver/src/test/java/org/neo4j/driver/internal/InternalStatementResultTest.java +++ b/driver/src/test/java/org/neo4j/driver/internal/InternalStatementResultTest.java @@ -358,7 +358,7 @@ private StatementResult createResult( int numberOfRecords ) when( connection.serverAddress() ).thenReturn( LOCAL_DEFAULT ); when( connection.serverVersion() ).thenReturn( ServerVersion.v3_2_0 ); PullAllResponseHandler pullAllHandler = - new SessionPullAllResponseHandler( statement, runHandler, connection, BookmarksHolder.NO_OP, METADATA_EXTRACTOR ); + new SessionPullAllResponseHandler( statement, runHandler, connection, BookmarkHolder.NO_OP, METADATA_EXTRACTOR ); for ( int i = 1; i <= numberOfRecords; i++ ) { 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 60ee1493e6..0493d8bf17 100644 --- a/driver/src/test/java/org/neo4j/driver/internal/InternalTransactionTest.java +++ b/driver/src/test/java/org/neo4j/driver/internal/InternalTransactionTest.java @@ -26,11 +26,11 @@ import java.util.function.Function; import java.util.stream.Stream; -import org.neo4j.driver.AccessMode; import org.neo4j.driver.Statement; import org.neo4j.driver.StatementResult; import org.neo4j.driver.Transaction; import org.neo4j.driver.Value; +import org.neo4j.driver.internal.async.ConnectionContext; import org.neo4j.driver.internal.messaging.v4.BoltProtocolV4; import org.neo4j.driver.internal.spi.Connection; import org.neo4j.driver.internal.spi.ConnectionProvider; @@ -67,7 +67,7 @@ void setUp() { connection = connectionMock( BoltProtocolV4.INSTANCE ); ConnectionProvider connectionProvider = mock( ConnectionProvider.class ); - when( connectionProvider.acquireConnection( any( String.class ), any( AccessMode.class ) ) ) + when( connectionProvider.acquireConnection( any( ConnectionContext.class ) ) ) .thenReturn( completedFuture( connection ) ); InternalSession session = new InternalSession( newSession( connectionProvider ) ); tx = session.beginTransaction(); 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 d8906112be..d85dabafa0 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 @@ -28,8 +28,8 @@ import org.neo4j.driver.Statement; import org.neo4j.driver.TransactionConfig; import org.neo4j.driver.exceptions.ClientException; -import org.neo4j.driver.internal.Bookmarks; -import org.neo4j.driver.internal.DefaultBookmarksHolder; +import org.neo4j.driver.internal.DefaultBookmarkHolder; +import org.neo4j.driver.internal.InternalBookmark; import org.neo4j.driver.internal.messaging.request.PullAllMessage; import org.neo4j.driver.internal.messaging.request.RunMessage; import org.neo4j.driver.internal.messaging.v4.BoltProtocolV4; @@ -148,7 +148,7 @@ void shouldOnlyQueueMessagesWhenNoBookmarkGiven() { Connection connection = connectionMock(); - beginTx( connection, Bookmarks.empty() ); + beginTx( connection, InternalBookmark.empty() ); verify( connection ).write( eq( new RunMessage( "BEGIN" ) ), any(), eq( PullAllMessage.PULL_ALL ), any() ); verify( connection, never() ).writeAndFlush( any(), any(), any(), any() ); @@ -157,13 +157,12 @@ void shouldOnlyQueueMessagesWhenNoBookmarkGiven() @Test void shouldFlushWhenBookmarkGiven() { - Bookmarks bookmarks = Bookmarks.from( "hi, I'm bookmark" ); + InternalBookmark bookmark = InternalBookmark.parse( "hi, I'm bookmark" ); Connection connection = connectionMock(); - beginTx( connection, bookmarks ); + beginTx( connection, bookmark ); - RunMessage expectedRunMessage = new RunMessage( "BEGIN", bookmarks.asBeginTransactionParameters() ); - verify( connection ).writeAndFlush( eq( expectedRunMessage ), any(), eq( PullAllMessage.PULL_ALL ), any() ); + verify( connection ).writeAndFlush( any(), any(), eq( PullAllMessage.PULL_ALL ), any() ); verify( connection, never() ).write( any(), any(), any(), any() ); } @@ -243,12 +242,12 @@ void shouldReleaseConnectionWhenBeginFails() { RuntimeException error = new RuntimeException( "Wrong bookmark!" ); Connection connection = connectionWithBegin( handler -> handler.onFailure( error ) ); - ExplicitTransaction tx = new ExplicitTransaction( connection, new DefaultBookmarksHolder() ); + ExplicitTransaction tx = new ExplicitTransaction( connection, new DefaultBookmarkHolder() ); - Bookmarks bookmarks = Bookmarks.from( "SomeBookmark" ); + InternalBookmark bookmark = InternalBookmark.parse( "SomeBookmark" ); TransactionConfig txConfig = TransactionConfig.empty(); - RuntimeException e = assertThrows( RuntimeException.class, () -> await( tx.beginAsync( bookmarks, txConfig ) ) ); + RuntimeException e = assertThrows( RuntimeException.class, () -> await( tx.beginAsync( bookmark, txConfig ) ) ); assertEquals( error, e ); verify( connection ).release(); @@ -258,12 +257,12 @@ void shouldReleaseConnectionWhenBeginFails() void shouldNotReleaseConnectionWhenBeginSucceeds() { Connection connection = connectionWithBegin( handler -> handler.onSuccess( emptyMap() ) ); - ExplicitTransaction tx = new ExplicitTransaction( connection, new DefaultBookmarksHolder() ); + ExplicitTransaction tx = new ExplicitTransaction( connection, new DefaultBookmarkHolder() ); - Bookmarks bookmarks = Bookmarks.from( "SomeBookmark" ); + InternalBookmark bookmark = InternalBookmark.parse( "SomeBookmark" ); TransactionConfig txConfig = TransactionConfig.empty(); - await( tx.beginAsync( bookmarks, txConfig ) ); + await( tx.beginAsync( bookmark, txConfig ) ); verify( connection, never() ).release(); } @@ -272,7 +271,7 @@ void shouldNotReleaseConnectionWhenBeginSucceeds() void shouldReleaseConnectionWhenTerminatedAndCommitted() { Connection connection = connectionMock(); - ExplicitTransaction tx = new ExplicitTransaction( connection, new DefaultBookmarksHolder() ); + ExplicitTransaction tx = new ExplicitTransaction( connection, new DefaultBookmarkHolder() ); tx.markTerminated(); @@ -286,7 +285,7 @@ void shouldReleaseConnectionWhenTerminatedAndCommitted() void shouldReleaseConnectionWhenTerminatedAndRolledBack() { Connection connection = connectionMock(); - ExplicitTransaction tx = new ExplicitTransaction( connection, new DefaultBookmarksHolder() ); + ExplicitTransaction tx = new ExplicitTransaction( connection, new DefaultBookmarkHolder() ); tx.markTerminated(); await( tx.rollbackAsync() ); @@ -298,7 +297,7 @@ void shouldReleaseConnectionWhenTerminatedAndRolledBack() void shouldReleaseConnectionWhenClose() throws Throwable { Connection connection = connectionMock(); - ExplicitTransaction tx = new ExplicitTransaction( connection, new DefaultBookmarksHolder() ); + ExplicitTransaction tx = new ExplicitTransaction( connection, new DefaultBookmarkHolder() ); await( tx.closeAsync() ); @@ -307,13 +306,13 @@ void shouldReleaseConnectionWhenClose() throws Throwable private static ExplicitTransaction beginTx( Connection connection ) { - return beginTx( connection, Bookmarks.empty() ); + return beginTx( connection, InternalBookmark.empty() ); } - private static ExplicitTransaction beginTx( Connection connection, Bookmarks initialBookmarks ) + private static ExplicitTransaction beginTx( Connection connection, InternalBookmark initialBookmark ) { - ExplicitTransaction tx = new ExplicitTransaction( connection, new DefaultBookmarksHolder() ); - return await( tx.beginAsync( initialBookmarks, TransactionConfig.empty() ) ); + ExplicitTransaction tx = new ExplicitTransaction( connection, new DefaultBookmarkHolder() ); + return await( tx.beginAsync( initialBookmark, TransactionConfig.empty() ) ); } private static Connection connectionWithBegin( Consumer beginBehaviour ) diff --git a/driver/src/test/java/org/neo4j/driver/internal/async/InternalAsyncSessionTest.java b/driver/src/test/java/org/neo4j/driver/internal/async/InternalAsyncSessionTest.java index 6eab9dc858..1f8b51227f 100644 --- a/driver/src/test/java/org/neo4j/driver/internal/async/InternalAsyncSessionTest.java +++ b/driver/src/test/java/org/neo4j/driver/internal/async/InternalAsyncSessionTest.java @@ -39,7 +39,7 @@ import org.neo4j.driver.async.StatementResultCursor; import org.neo4j.driver.exceptions.ServiceUnavailableException; import org.neo4j.driver.exceptions.SessionExpiredException; -import org.neo4j.driver.internal.Bookmarks; +import org.neo4j.driver.internal.InternalBookmark; import org.neo4j.driver.internal.InternalRecord; import org.neo4j.driver.internal.messaging.v4.BoltProtocolV4; import org.neo4j.driver.internal.retry.RetryLogic; @@ -59,7 +59,6 @@ import static org.junit.jupiter.api.Assertions.assertEquals; 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.never; import static org.mockito.Mockito.spy; @@ -92,7 +91,7 @@ void setUp() { connection = connectionMock( BoltProtocolV4.INSTANCE ); connectionProvider = mock( ConnectionProvider.class ); - when( connectionProvider.acquireConnection( any( String.class ), any( AccessMode.class ) ) ) + when( connectionProvider.acquireConnection( any( ConnectionContext.class ) ) ) .thenReturn( completedFuture( connection ) ); session = newSession( connectionProvider ); asyncSession = new InternalAsyncSession( session ); @@ -236,7 +235,7 @@ void shouldCloseSession() throws Throwable @Test void shouldReturnBookmark() throws Throwable { - session = newSession( connectionProvider, Bookmarks.from( "Bookmark1" ) ); + session = newSession( connectionProvider, InternalBookmark.parse( "Bookmark1" ) ); asyncSession = new InternalAsyncSession( session ); assertThat( asyncSession.lastBookmark(), equalTo( session.lastBookmark() )); @@ -253,7 +252,7 @@ private void testTxRollbackWhenThrows( AccessMode transactionMode ) Exception e = assertThrows( Exception.class, () -> executeTransaction( asyncSession, transactionMode, work ) ); assertEquals( error, e ); - verify( connectionProvider ).acquireConnection( any( String.class ), eq( transactionMode ) ); + verify( connectionProvider ).acquireConnection( any( ConnectionContext.class ) ); verifyBeginTx( connection ); verifyRollbackTx( connection ); } diff --git a/driver/src/test/java/org/neo4j/driver/internal/async/InternalAsyncTransactionTest.java b/driver/src/test/java/org/neo4j/driver/internal/async/InternalAsyncTransactionTest.java index fca3c84275..ff2eac586b 100644 --- a/driver/src/test/java/org/neo4j/driver/internal/async/InternalAsyncTransactionTest.java +++ b/driver/src/test/java/org/neo4j/driver/internal/async/InternalAsyncTransactionTest.java @@ -27,7 +27,6 @@ import java.util.function.Function; import java.util.stream.Stream; -import org.neo4j.driver.AccessMode; import org.neo4j.driver.Statement; import org.neo4j.driver.Value; import org.neo4j.driver.async.AsyncTransaction; @@ -72,7 +71,7 @@ void setUp() { connection = connectionMock( BoltProtocolV4.INSTANCE ); ConnectionProvider connectionProvider = mock( ConnectionProvider.class ); - when( connectionProvider.acquireConnection( any( String.class ), any( AccessMode.class ) ) ) + when( connectionProvider.acquireConnection( any( ConnectionContext.class ) ) ) .thenReturn( completedFuture( connection ) ); InternalAsyncSession session = new InternalAsyncSession( newSession( connectionProvider ) ); tx = (InternalAsyncTransaction) await( session.beginTransactionAsync() ); 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 00da69a766..b75a03af8a 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 @@ -24,11 +24,10 @@ import java.lang.reflect.Method; -import org.neo4j.driver.AccessMode; import org.neo4j.driver.Logger; import org.neo4j.driver.Logging; import org.neo4j.driver.TransactionConfig; -import org.neo4j.driver.internal.DefaultBookmarksHolder; +import org.neo4j.driver.internal.DefaultBookmarkHolder; import org.neo4j.driver.internal.spi.Connection; import org.neo4j.driver.internal.spi.ConnectionProvider; import org.neo4j.driver.internal.util.FixedRetryLogic; @@ -98,14 +97,14 @@ private static void finalize( NetworkSession session ) throws Exception private static LeakLoggingNetworkSession newSession( Logging logging, boolean openConnection ) { return new LeakLoggingNetworkSession( connectionProviderMock( openConnection ), new FixedRetryLogic( 0 ), ABSENT_DB_NAME, READ, - new DefaultBookmarksHolder(), logging ); + new DefaultBookmarkHolder(), logging ); } private static ConnectionProvider connectionProviderMock( boolean openConnection ) { ConnectionProvider provider = mock( ConnectionProvider.class ); Connection connection = connectionMock( openConnection ); - when( provider.acquireConnection( any( String.class ), any( AccessMode.class ) ) ).thenReturn( completedFuture( connection ) ); + when( provider.acquireConnection( any( ConnectionContext.class ) ) ).thenReturn( completedFuture( connection ) ); return provider; } 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 57771ce4d2..cdad603b14 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 @@ -22,6 +22,7 @@ import org.junit.jupiter.api.Test; import org.junit.jupiter.params.ParameterizedTest; import org.junit.jupiter.params.provider.ValueSource; +import org.mockito.ArgumentCaptor; import org.mockito.InOrder; import org.neo4j.driver.AccessMode; @@ -29,7 +30,8 @@ import org.neo4j.driver.TransactionConfig; import org.neo4j.driver.async.StatementResultCursor; import org.neo4j.driver.exceptions.ClientException; -import org.neo4j.driver.internal.Bookmarks; +import org.neo4j.driver.internal.Bookmark; +import org.neo4j.driver.internal.InternalBookmark; import org.neo4j.driver.internal.messaging.BoltProtocol; import org.neo4j.driver.internal.messaging.request.PullMessage; import org.neo4j.driver.internal.messaging.request.RunWithMetadataMessage; @@ -44,7 +46,6 @@ import static org.hamcrest.junit.MatcherAssert.assertThat; import static org.junit.jupiter.api.Assertions.assertEquals; import static org.junit.jupiter.api.Assertions.assertNotNull; -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.mockito.ArgumentMatchers.any; @@ -59,7 +60,6 @@ import static org.mockito.Mockito.when; import static org.neo4j.driver.AccessMode.READ; import static org.neo4j.driver.AccessMode.WRITE; -import static org.neo4j.driver.internal.messaging.request.MultiDatabaseUtil.ABSENT_DB_NAME; import static org.neo4j.driver.internal.util.Futures.failedFuture; import static org.neo4j.driver.util.TestUtil.await; import static org.neo4j.driver.util.TestUtil.connectionMock; @@ -83,7 +83,7 @@ void setUp() { connection = connectionMock( BoltProtocolV4.INSTANCE ); connectionProvider = mock( ConnectionProvider.class ); - when( connectionProvider.acquireConnection( any( String.class ), any( AccessMode.class ) ) ) + when( connectionProvider.acquireConnection( any( ConnectionContext.class ) ) ) .thenReturn( completedFuture( connection ) ); session = newSession( connectionProvider ); } @@ -181,7 +181,7 @@ void acquiresNewConnectionForRun() { run( session, "RETURN 1" ); - verify( connectionProvider ).acquireConnection( any( String.class ), any( AccessMode.class ) ); + verify( connectionProvider ).acquireConnection( any( ConnectionContext.class ) ); } @Test @@ -205,7 +205,7 @@ void resetDoesNothingWhenNoTransactionAndNoConnection() { await( session.resetAsync() ); - verify( connectionProvider, never() ).acquireConnection( any( String.class ), any( AccessMode.class ) ); + verify( connectionProvider, never() ).acquireConnection( any( ConnectionContext.class ) ); } @Test @@ -215,7 +215,7 @@ void closeWithoutConnection() close( session ); - verify( connectionProvider, never() ).acquireConnection( any( String.class ), any( AccessMode.class ) ); + verify( connectionProvider, never() ).acquireConnection( any( ConnectionContext.class ) ); } @Test @@ -224,13 +224,13 @@ void acquiresNewConnectionForBeginTx() ExplicitTransaction tx = beginTransaction( session ); assertNotNull( tx ); - verify( connectionProvider ).acquireConnection( any( String.class ), any( AccessMode.class ) ); + verify( connectionProvider ).acquireConnection( any( ConnectionContext.class ) ); } @Test void updatesBookmarkWhenTxIsClosed() { - Bookmarks bookmarkAfterCommit = Bookmarks.from( "TheBookmark" ); + Bookmark bookmarkAfterCommit = InternalBookmark.parse( "TheBookmark" ); BoltProtocol protocol = spy( BoltProtocolV4.INSTANCE ); doReturn( completedFuture( bookmarkAfterCommit ) ).when( protocol ).commitTransaction( any( Connection.class ) ); @@ -238,11 +238,13 @@ void updatesBookmarkWhenTxIsClosed() when( connection.protocol() ).thenReturn( protocol ); ExplicitTransaction tx = beginTransaction( session ); - assertNull( session.lastBookmark() ); + assertThat( session.lastBookmark(), instanceOf( InternalBookmark.class ) ); + InternalBookmark bookmark = (InternalBookmark) session.lastBookmark(); + assertTrue( bookmark.isEmpty() ); tx.success(); await( tx.closeAsync() ); - assertEquals( "TheBookmark", session.lastBookmark() ); + assertEquals( bookmarkAfterCommit, session.lastBookmark() ); } @Test @@ -254,7 +256,7 @@ void releasesConnectionWhenTxIsClosed() ExplicitTransaction tx = beginTransaction( session ); await( tx.runAsync( new Statement( query ), false ) ); - verify( connectionProvider ).acquireConnection( any( String.class ), any( AccessMode.class ) ); + verify( connectionProvider ).acquireConnection( any( ConnectionContext.class ) ); verifyRunAndPull( connection, query ); await( tx.closeAsync() ); @@ -264,58 +266,67 @@ void releasesConnectionWhenTxIsClosed() @Test void bookmarkIsPropagatedFromSession() { - Bookmarks bookmarks = Bookmarks.from( "Bookmarks" ); - NetworkSession session = newSession( connectionProvider, bookmarks ); + InternalBookmark bookmark = InternalBookmark.parse( "Bookmarks" ); + NetworkSession session = newSession( connectionProvider, bookmark ); ExplicitTransaction tx = beginTransaction( session ); assertNotNull( tx ); - verifyBeginTx( connection, bookmarks ); + verifyBeginTx( connection, bookmark ); } @Test void bookmarkIsPropagatedBetweenTransactions() { - Bookmarks bookmarks1 = Bookmarks.from( "Bookmark1" ); - Bookmarks bookmarks2 = Bookmarks.from( "Bookmark2" ); + InternalBookmark bookmark1 = InternalBookmark.parse( "Bookmark1" ); + Bookmark bookmark2 = InternalBookmark.parse( "Bookmark2" ); NetworkSession session = newSession( connectionProvider ); BoltProtocol protocol = spy( BoltProtocolV4.INSTANCE ); - doReturn( completedFuture( bookmarks1 ), completedFuture( bookmarks2 ) ).when( protocol ).commitTransaction( any( Connection.class ) ); + doReturn( completedFuture( bookmark1 ), completedFuture( bookmark2 ) ).when( protocol ).commitTransaction( any( Connection.class ) ); when( connection.protocol() ).thenReturn( protocol ); ExplicitTransaction tx1 = beginTransaction( session ); tx1.success(); await( tx1.closeAsync() ); - assertEquals( bookmarks1, Bookmarks.from( session.lastBookmark() ) ); + assertEquals( bookmark1, session.lastBookmark() ); ExplicitTransaction tx2 = beginTransaction( session ); - verifyBeginTx( connection, bookmarks1 ); + verifyBeginTx( connection, bookmark1 ); tx2.success(); await( tx2.closeAsync() ); - assertEquals( bookmarks2, Bookmarks.from( session.lastBookmark() ) ); + assertEquals( bookmark2, session.lastBookmark() ); } @Test - void accessModeUsedToAcquireConnections() + void accessModeUsedToAcquireReadConnections() { - NetworkSession session1 = newSession( connectionProvider, READ ); - beginTransaction( session1 ); - verify( connectionProvider ).acquireConnection( ABSENT_DB_NAME, READ ); + accessModeUsedToAcquireConnections( READ ); + } + + @Test + void accessModeUsedToAcquireWriteConnections() + { + accessModeUsedToAcquireConnections( WRITE ); + } - NetworkSession session2 = newSession( connectionProvider, WRITE ); + private void accessModeUsedToAcquireConnections( AccessMode mode ) + { + NetworkSession session2 = newSession( connectionProvider, mode ); beginTransaction( session2 ); - verify( connectionProvider ).acquireConnection( ABSENT_DB_NAME, WRITE ); + ArgumentCaptor argument = ArgumentCaptor.forClass( ConnectionContext.class ); + verify( connectionProvider ).acquireConnection( argument.capture() ); + assertEquals( mode, argument.getValue().mode() ); } @Test void testPassingNoBookmarkShouldRetainBookmark() { - NetworkSession session = newSession( connectionProvider, Bookmarks.from( "X" ) ); + NetworkSession session = newSession( connectionProvider, InternalBookmark.parse( "X" ) ); beginTransaction( session ); - assertThat( session.lastBookmark(), equalTo( "X" ) ); + assertThat( session.lastBookmark(), equalTo( InternalBookmark.parse( "X" ) ) ); } @Test @@ -332,16 +343,18 @@ void connectionShouldBeResetAfterSessionReset() } @Test - void shouldHaveNullLastBookmarkInitially() + void shouldHaveEmptyLastBookmarkInitially() { - assertNull( session.lastBookmark() ); + assertThat( session.lastBookmark(), instanceOf( InternalBookmark.class ) ); + InternalBookmark bookmark = (InternalBookmark) session.lastBookmark(); + assertTrue( bookmark.isEmpty() ); } @Test void shouldDoNothingWhenClosingWithoutAcquiredConnection() { RuntimeException error = new RuntimeException( "Hi" ); - when( connectionProvider.acquireConnection( any( String.class ), any( AccessMode.class ) ) ).thenReturn( failedFuture( error ) ); + when( connectionProvider.acquireConnection( any( ConnectionContext.class ) ) ).thenReturn( failedFuture( error ) ); Exception e = assertThrows( Exception.class, () -> run( session, "RETURN 1" ) ); assertEquals( error, e ); @@ -353,7 +366,7 @@ void shouldDoNothingWhenClosingWithoutAcquiredConnection() void shouldRunAfterRunFailureToAcquireConnection() { RuntimeException error = new RuntimeException( "Hi" ); - when( connectionProvider.acquireConnection( any( String.class ), any( AccessMode.class ) ) ) + when( connectionProvider.acquireConnection( any( ConnectionContext.class ) ) ) .thenReturn( failedFuture( error ) ).thenReturn( completedFuture( connection ) ); Exception e = assertThrows( Exception.class, () -> run( session,"RETURN 1" ) ); @@ -361,7 +374,7 @@ void shouldRunAfterRunFailureToAcquireConnection() run( session, "RETURN 2" ); - verify( connectionProvider, times( 2 ) ).acquireConnection( any( String.class ), any( AccessMode.class ) ); + verify( connectionProvider, times( 2 ) ).acquireConnection( any( ConnectionContext.class ) ); verifyRunAndPull( connection, "RETURN 2" ); } @@ -373,19 +386,19 @@ void shouldRunAfterBeginTxFailureOnBookmark() setupFailingBegin( connection1, error ); Connection connection2 = connectionMock( BoltProtocolV4.INSTANCE ); - when( connectionProvider.acquireConnection( any( String.class ), any( AccessMode.class ) ) ) + when( connectionProvider.acquireConnection( any( ConnectionContext.class ) ) ) .thenReturn( completedFuture( connection1 ) ).thenReturn( completedFuture( connection2 ) ); - Bookmarks bookmarks = Bookmarks.from( "neo4j:bookmark:v1:tx42" ); - NetworkSession session = newSession( connectionProvider, bookmarks ); + InternalBookmark bookmark = InternalBookmark.parse( "neo4j:bookmark:v1:tx42" ); + NetworkSession session = newSession( connectionProvider, bookmark ); Exception e = assertThrows( Exception.class, () -> beginTransaction( session ) ); assertEquals( error, e ); run( session, "RETURN 2" ); - verify( connectionProvider, times( 2 ) ).acquireConnection( any( String.class ), any( AccessMode.class ) ); - verifyBeginTx( connection1, bookmarks ); + verify( connectionProvider, times( 2 ) ).acquireConnection( any( ConnectionContext.class ) ); + verifyBeginTx( connection1, bookmark ); verifyRunAndPull( connection2, "RETURN 2" ); } @@ -397,27 +410,27 @@ void shouldBeginTxAfterBeginTxFailureOnBookmark() setupFailingBegin( connection1, error ); Connection connection2 = connectionMock( BoltProtocolV4.INSTANCE ); - when( connectionProvider.acquireConnection( any( String.class ), any( AccessMode.class ) ) ) + when( connectionProvider.acquireConnection( any( ConnectionContext.class ) ) ) .thenReturn( completedFuture( connection1 ) ).thenReturn( completedFuture( connection2 ) ); - Bookmarks bookmarks = Bookmarks.from( "neo4j:bookmark:v1:tx42" ); - NetworkSession session = newSession( connectionProvider, bookmarks ); + InternalBookmark bookmark = InternalBookmark.parse( "neo4j:bookmark:v1:tx42" ); + NetworkSession session = newSession( connectionProvider, bookmark ); Exception e = assertThrows( Exception.class, () -> beginTransaction( session ) ); assertEquals( error, e ); beginTransaction( session ); - verify( connectionProvider, times( 2 ) ).acquireConnection( any( String.class ), any( AccessMode.class ) ); - verifyBeginTx( connection1, bookmarks ); - verifyBeginTx( connection2, bookmarks ); + verify( connectionProvider, times( 2 ) ).acquireConnection( any( ConnectionContext.class ) ); + verifyBeginTx( connection1, bookmark ); + verifyBeginTx( connection2, bookmark ); } @Test void shouldBeginTxAfterRunFailureToAcquireConnection() { RuntimeException error = new RuntimeException( "Hi" ); - when( connectionProvider.acquireConnection( any( String.class ), any( AccessMode.class ) ) ) + when( connectionProvider.acquireConnection( any( ConnectionContext.class ) ) ) .thenReturn( failedFuture( error ) ).thenReturn( completedFuture( connection ) ); Exception e = assertThrows( Exception.class, () -> run( session, "RETURN 1" ) ); @@ -425,7 +438,7 @@ void shouldBeginTxAfterRunFailureToAcquireConnection() beginTransaction( session ); - verify( connectionProvider, times( 2 ) ).acquireConnection( any( String.class ), any( AccessMode.class ) ); + verify( connectionProvider, times( 2 ) ).acquireConnection( any( ConnectionContext.class ) ); verifyBeginTx( connection ); } diff --git a/driver/src/test/java/org/neo4j/driver/internal/cluster/AbstractRoutingProcedureRunnerTest.java b/driver/src/test/java/org/neo4j/driver/internal/cluster/AbstractRoutingProcedureRunnerTest.java new file mode 100644 index 0000000000..934fdf14db --- /dev/null +++ b/driver/src/test/java/org/neo4j/driver/internal/cluster/AbstractRoutingProcedureRunnerTest.java @@ -0,0 +1,107 @@ +/* + * 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.cluster; + +import org.junit.jupiter.api.Test; + +import java.util.List; +import java.util.concurrent.CompletionStage; + +import org.neo4j.driver.Record; +import org.neo4j.driver.exceptions.ClientException; +import org.neo4j.driver.internal.spi.Connection; + +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertFalse; +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.verify; +import static org.mockito.Mockito.when; +import static org.neo4j.driver.internal.InternalBookmark.empty; +import static org.neo4j.driver.internal.messaging.request.MultiDatabaseUtil.ABSENT_DB_NAME; +import static org.neo4j.driver.internal.util.Futures.completedWithNull; +import static org.neo4j.driver.internal.util.Futures.failedFuture; +import static org.neo4j.driver.util.TestUtil.await; + +abstract class AbstractRoutingProcedureRunnerTest +{ + @Test + void shouldReturnFailedResponseOnClientException() + { + ClientException error = new ClientException( "Hi" ); + RoutingProcedureRunner runner = routingProcedureRunner( RoutingContext.EMPTY, failedFuture( error ) ); + + RoutingProcedureResponse response = await( runner.run( connection(), ABSENT_DB_NAME, empty() ) ); + + assertFalse( response.isSuccess() ); + assertEquals( error, response.error() ); + } + + @Test + void shouldReturnFailedStageOnError() + { + Exception error = new Exception( "Hi" ); + RoutingProcedureRunner runner = routingProcedureRunner( RoutingContext.EMPTY, failedFuture( error ) ); + + Exception e = assertThrows( Exception.class, () -> await( runner.run( connection(), ABSENT_DB_NAME, empty() ) ) ); + assertEquals( error, e ); + } + + @Test + void shouldReleaseConnectionOnSuccess() + { + RoutingProcedureRunner runner = routingProcedureRunner( RoutingContext.EMPTY ); + + Connection connection = connection(); + RoutingProcedureResponse response = await( runner.run( connection, ABSENT_DB_NAME, empty() ) ); + + assertTrue( response.isSuccess() ); + verify( connection ).release(); + } + + @Test + void shouldPropagateReleaseError() + { + RoutingProcedureRunner runner = routingProcedureRunner( RoutingContext.EMPTY ); + + RuntimeException releaseError = new RuntimeException( "Release failed" ); + Connection connection = connection( failedFuture( releaseError ) ); + + RuntimeException e = assertThrows( RuntimeException.class, () -> await( runner.run( connection, ABSENT_DB_NAME, empty() ) ) ); + assertEquals( releaseError, e ); + verify( connection ).release(); + } + + abstract RoutingProcedureRunner routingProcedureRunner( RoutingContext context ); + + abstract RoutingProcedureRunner routingProcedureRunner( RoutingContext context, CompletionStage> runProcedureResult ); + + static Connection connection() + { + return connection( completedWithNull() ); + } + + static Connection connection( CompletionStage releaseStage ) + { + Connection connection = mock( Connection.class ); + when( connection.release() ).thenReturn( releaseStage ); + return connection; + } +} diff --git a/driver/src/test/java/org/neo4j/driver/internal/cluster/MultiDatabasesRoutingProcedureRunnerTest.java b/driver/src/test/java/org/neo4j/driver/internal/cluster/MultiDatabasesRoutingProcedureRunnerTest.java new file mode 100644 index 0000000000..8ac274848d --- /dev/null +++ b/driver/src/test/java/org/neo4j/driver/internal/cluster/MultiDatabasesRoutingProcedureRunnerTest.java @@ -0,0 +1,147 @@ +/* + * 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.cluster; + +import org.junit.jupiter.params.ParameterizedTest; +import org.junit.jupiter.params.provider.ValueSource; + +import java.net.URI; +import java.util.List; +import java.util.Map; +import java.util.Objects; +import java.util.concurrent.CompletionStage; + +import org.neo4j.driver.AccessMode; +import org.neo4j.driver.Record; +import org.neo4j.driver.Statement; +import org.neo4j.driver.Value; +import org.neo4j.driver.internal.BookmarkHolder; +import org.neo4j.driver.internal.ReadOnlyBookmarkHolder; +import org.neo4j.driver.internal.spi.Connection; + +import static java.util.Collections.EMPTY_MAP; +import static java.util.Collections.singletonList; +import static java.util.concurrent.CompletableFuture.completedFuture; +import static org.hamcrest.Matchers.equalTo; +import static org.hamcrest.core.IsInstanceOf.instanceOf; +import static org.hamcrest.junit.MatcherAssert.assertThat; +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertTrue; +import static org.mockito.Mockito.mock; +import static org.neo4j.driver.Values.parameters; +import static org.neo4j.driver.internal.InternalBookmark.empty; +import static org.neo4j.driver.internal.cluster.MultiDatabasesRoutingProcedureRunner.DATABASE_NAME; +import static org.neo4j.driver.internal.cluster.MultiDatabasesRoutingProcedureRunner.MULTI_DB_GET_ROUTING_TABLE; +import static org.neo4j.driver.internal.cluster.RoutingProcedureRunner.ROUTING_CONTEXT; +import static org.neo4j.driver.internal.messaging.request.MultiDatabaseUtil.ABSENT_DB_NAME; +import static org.neo4j.driver.internal.messaging.request.MultiDatabaseUtil.SYSTEM_DB_NAME; +import static org.neo4j.driver.util.TestUtil.await; + +class MultiDatabasesRoutingProcedureRunnerTest extends AbstractRoutingProcedureRunnerTest +{ + @ParameterizedTest + @ValueSource( strings = {ABSENT_DB_NAME, SYSTEM_DB_NAME, " this is a db name "} ) + void shouldCallGetRoutingTableWithEmptyMapOnSystemDatabaseForDatabase( String db ) + { + TestRoutingProcedureRunner runner = new TestRoutingProcedureRunner( RoutingContext.EMPTY ); + RoutingProcedureResponse response = await( runner.run( connection(), db, empty() ) ); + + assertTrue( response.isSuccess() ); + assertEquals( 1, response.records().size() ); + + assertThat( runner.bookmarkHolder, instanceOf( ReadOnlyBookmarkHolder.class ) ); + assertThat( runner.connection.databaseName(), equalTo( SYSTEM_DB_NAME ) ); + assertThat( runner.connection.mode(), equalTo( AccessMode.READ ) ); + + Statement statement = generateMultiDatabaseRoutingStatement( EMPTY_MAP, db ); + assertThat( runner.procedure, equalTo( statement ) ); + } + + @ParameterizedTest + @ValueSource( strings = {ABSENT_DB_NAME, SYSTEM_DB_NAME, " this is a db name "} ) + void shouldCallGetRoutingTableWithParamOnSystemDatabaseForDatabase( String db ) + { + URI uri = URI.create( "neo4j://localhost/?key1=value1&key2=value2" ); + RoutingContext context = new RoutingContext( uri ); + + TestRoutingProcedureRunner runner = new TestRoutingProcedureRunner( context ); + RoutingProcedureResponse response = await( runner.run( connection(), db, empty() ) ); + + assertTrue( response.isSuccess() ); + assertEquals( 1, response.records().size() ); + + assertThat( runner.bookmarkHolder, instanceOf( ReadOnlyBookmarkHolder.class ) ); + assertThat( runner.connection.databaseName(), equalTo( SYSTEM_DB_NAME ) ); + assertThat( runner.connection.mode(), equalTo( AccessMode.READ ) ); + + Statement statement = generateMultiDatabaseRoutingStatement( context.asMap(), db ); + assertThat( response.procedure(), equalTo( statement ) ); + assertThat( runner.procedure, equalTo( statement ) ); + } + + @Override + RoutingProcedureRunner routingProcedureRunner( RoutingContext context ) + { + return new TestRoutingProcedureRunner( context ); + } + + @Override + RoutingProcedureRunner routingProcedureRunner( RoutingContext context, CompletionStage> runProcedureResult ) + { + return new TestRoutingProcedureRunner( context, runProcedureResult ); + } + + private static Statement generateMultiDatabaseRoutingStatement( Map context, String db ) + { + if ( Objects.equals( ABSENT_DB_NAME, db ) ) + { + db = null; + } + Value parameters = parameters( ROUTING_CONTEXT, context, DATABASE_NAME, db ); + return new Statement( MULTI_DB_GET_ROUTING_TABLE, parameters ); + } + + private static class TestRoutingProcedureRunner extends MultiDatabasesRoutingProcedureRunner + { + final CompletionStage> runProcedureResult; + private Connection connection; + private Statement procedure; + private BookmarkHolder bookmarkHolder; + + TestRoutingProcedureRunner( RoutingContext context ) + { + this( context, completedFuture( singletonList( mock( Record.class ) ) ) ); + } + + TestRoutingProcedureRunner( RoutingContext context, CompletionStage> runProcedureResult ) + { + super( context ); + this.runProcedureResult = runProcedureResult; + } + + @Override + CompletionStage> runProcedure( Connection connection, Statement procedure, BookmarkHolder bookmarkHolder ) + { + this.connection = connection; + this.procedure = procedure; + this.bookmarkHolder = bookmarkHolder; + return runProcedureResult; + } + } +} diff --git a/driver/src/test/java/org/neo4j/driver/internal/cluster/RediscoveryTest.java b/driver/src/test/java/org/neo4j/driver/internal/cluster/RediscoveryTest.java index 21d1b93c30..3cbc413235 100644 --- a/driver/src/test/java/org/neo4j/driver/internal/cluster/RediscoveryTest.java +++ b/driver/src/test/java/org/neo4j/driver/internal/cluster/RediscoveryTest.java @@ -24,7 +24,6 @@ import java.io.IOException; import java.util.HashMap; import java.util.Map; -import java.util.concurrent.CompletionStage; import org.neo4j.driver.Logger; import org.neo4j.driver.exceptions.AuthenticationException; @@ -32,6 +31,7 @@ import org.neo4j.driver.exceptions.ServiceUnavailableException; import org.neo4j.driver.exceptions.SessionExpiredException; import org.neo4j.driver.internal.BoltServerAddress; +import org.neo4j.driver.internal.InternalBookmark; import org.neo4j.driver.internal.spi.Connection; import org.neo4j.driver.internal.spi.ConnectionPool; import org.neo4j.driver.internal.util.FakeClock; @@ -54,6 +54,7 @@ import static org.mockito.Mockito.times; import static org.mockito.Mockito.verify; import static org.mockito.Mockito.when; +import static org.neo4j.driver.internal.InternalBookmark.empty; import static org.neo4j.driver.internal.logging.DevNullLogger.DEV_NULL_LOGGER; import static org.neo4j.driver.internal.messaging.request.MultiDatabaseUtil.ABSENT_DB_NAME; import static org.neo4j.driver.internal.util.ClusterCompositionUtil.A; @@ -82,7 +83,7 @@ void shouldUseFirstRouterInTable() Rediscovery rediscovery = newRediscovery( A, compositionProvider, mock( ServerAddressResolver.class ) ); RoutingTable table = routingTableMock( B ); - ClusterComposition actualComposition = await( rediscovery.lookupClusterComposition( table, pool ) ); + ClusterComposition actualComposition = await( rediscovery.lookupClusterComposition( table, pool, empty() ) ); assertEquals( expectedComposition, actualComposition ); verify( table, never() ).forget( B ); @@ -103,7 +104,7 @@ void shouldSkipFailingRouters() Rediscovery rediscovery = newRediscovery( A, compositionProvider, mock( ServerAddressResolver.class ) ); RoutingTable table = routingTableMock( A, B, C ); - ClusterComposition actualComposition = await( rediscovery.lookupClusterComposition( table, pool ) ); + ClusterComposition actualComposition = await( rediscovery.lookupClusterComposition( table, pool, empty() ) ); assertEquals( expectedComposition, actualComposition ); verify( table ).forget( A ); @@ -125,7 +126,8 @@ void shouldFailImmediatelyOnAuthError() Rediscovery rediscovery = newRediscovery( A, compositionProvider, mock( ServerAddressResolver.class ) ); RoutingTable table = routingTableMock( A, B, C ); - AuthenticationException error = assertThrows( AuthenticationException.class, () -> await( rediscovery.lookupClusterComposition( table, pool ) ) ); + AuthenticationException error = assertThrows( AuthenticationException.class, + () -> await( rediscovery.lookupClusterComposition( table, pool, empty() ) ) ); assertEquals( authError, error ); verify( table ).forget( A ); } @@ -147,7 +149,7 @@ void shouldFallbackToInitialRouterWhenKnownRoutersFail() Rediscovery rediscovery = newRediscovery( initialRouter, compositionProvider, resolver ); RoutingTable table = routingTableMock( B, C ); - ClusterComposition actualComposition = await( rediscovery.lookupClusterComposition( table, pool ) ); + ClusterComposition actualComposition = await( rediscovery.lookupClusterComposition( table, pool, empty() ) ); assertEquals( expectedComposition, actualComposition ); verify( table ).forget( B ); @@ -172,7 +174,7 @@ void shouldFailImmediatelyWhenClusterCompositionProviderReturnsFailure() RoutingTable table = routingTableMock( B, C ); // When - ClusterComposition composition = await( rediscovery.lookupClusterComposition( table, pool ) ); + ClusterComposition composition = await( rediscovery.lookupClusterComposition( table, pool, empty() ) ); assertEquals( validComposition, composition ); verify( logger ).warn( String.format( "Failed to update routing table with server '%s'.", B ), protocolError ); @@ -197,7 +199,7 @@ void shouldResolveInitialRouterAddress() Rediscovery rediscovery = newRediscovery( initialRouter, compositionProvider, resolver ); RoutingTable table = routingTableMock( B, C ); - ClusterComposition actualComposition = await( rediscovery.lookupClusterComposition( table, pool ) ); + ClusterComposition actualComposition = await( rediscovery.lookupClusterComposition( table, pool, empty() ) ); assertEquals( expectedComposition, actualComposition ); verify( table ).forget( B ); @@ -226,7 +228,7 @@ void shouldResolveInitialRouterAddressUsingCustomResolver() Rediscovery rediscovery = newRediscovery( A, compositionProvider, resolver ); RoutingTable table = routingTableMock( B, C ); - ClusterComposition actualComposition = await( rediscovery.lookupClusterComposition( table, pool ) ); + ClusterComposition actualComposition = await( rediscovery.lookupClusterComposition( table, pool, empty() ) ); assertEquals( expectedComposition, actualComposition ); verify( table ).forget( B ); @@ -249,7 +251,7 @@ void shouldPropagateFailureWhenResolverFails() Rediscovery rediscovery = newRediscovery( A, compositionProvider, resolver ); RoutingTable table = routingTableMock(); - RuntimeException error = assertThrows( RuntimeException.class, () -> await( rediscovery.lookupClusterComposition( table, pool ) ) ); + RuntimeException error = assertThrows( RuntimeException.class, () -> await( rediscovery.lookupClusterComposition( table, pool, empty() ) ) ); assertEquals( "Resolver fails!", error.getMessage() ); verify( resolver ).resolve( A ); @@ -268,7 +270,7 @@ void shouldFailWhenNoRoutersRespond() Rediscovery rediscovery = newRediscovery( A, compositionProvider, mock( ServerAddressResolver.class ) ); RoutingTable table = routingTableMock( A, B, C ); - ServiceUnavailableException e = assertThrows( ServiceUnavailableException.class, () -> await( rediscovery.lookupClusterComposition( table, pool ) ) ); + ServiceUnavailableException e = assertThrows( ServiceUnavailableException.class, () -> await( rediscovery.lookupClusterComposition( table, pool, empty() ) ) ); assertThat( e.getMessage(), containsString( "Could not perform discovery" ) ); } @@ -290,7 +292,7 @@ void shouldUseInitialRouterAfterDiscoveryReturnsNoWriters() RoutingTable table = new ClusterRoutingTable( ABSENT_DB_NAME, new FakeClock() ); table.update( noWritersComposition ); - ClusterComposition composition2 = await( rediscovery.lookupClusterComposition( table, pool ) ); + ClusterComposition composition2 = await( rediscovery.lookupClusterComposition( table, pool, empty() ) ); assertEquals( validComposition, composition2 ); } @@ -309,7 +311,7 @@ void shouldUseInitialRouterToStartWith() Rediscovery rediscovery = newRediscovery( initialRouter, compositionProvider, resolver ); RoutingTable table = routingTableMock( true, B, C, D ); - ClusterComposition composition = await( rediscovery.lookupClusterComposition( table, pool ) ); + ClusterComposition composition = await( rediscovery.lookupClusterComposition( table, pool, empty() ) ); assertEquals( validComposition, composition ); } @@ -330,7 +332,7 @@ void shouldUseKnownRoutersWhenInitialRouterFails() Rediscovery rediscovery = newRediscovery( initialRouter, compositionProvider, resolver ); RoutingTable table = routingTableMock( true, D, E ); - ClusterComposition composition = await( rediscovery.lookupClusterComposition( table, pool ) ); + ClusterComposition composition = await( rediscovery.lookupClusterComposition( table, pool, empty() ) ); assertEquals( validComposition, composition ); verify( table ).forget( initialRouter ); verify( table ).forget( D ); @@ -360,7 +362,7 @@ void shouldRetryConfiguredNumberOfTimesWithDelay() Rediscovery rediscovery = new RediscoveryImpl( A, settings, compositionProvider, eventExecutor, resolver, DEV_NULL_LOGGER ); RoutingTable table = routingTableMock(A, B ); - ClusterComposition actualComposition = await( rediscovery.lookupClusterComposition( table, pool ) ); + ClusterComposition actualComposition = await( rediscovery.lookupClusterComposition( table, pool, empty() ) ); assertEquals( expectedComposition, actualComposition ); verify( table, times( maxRoutingFailures ) ).forget( A ); @@ -384,7 +386,7 @@ void shouldNotLogWhenSingleRetryAttemptFails() Rediscovery rediscovery = new RediscoveryImpl( A, settings, compositionProvider, eventExecutor, resolver, logger ); RoutingTable table = routingTableMock( A ); - ServiceUnavailableException e = assertThrows( ServiceUnavailableException.class, () -> await( rediscovery.lookupClusterComposition( table, pool ) ) ); + ServiceUnavailableException e = assertThrows( ServiceUnavailableException.class, () -> await( rediscovery.lookupClusterComposition( table, pool, empty() ) ) ); assertThat( e.getMessage(), containsString( "Could not perform discovery" ) ); // rediscovery should not log about retries and should not schedule any retries @@ -410,10 +412,10 @@ private static ClusterCompositionProvider compositionProviderMock( Map responsesByAddress ) { ClusterCompositionProvider provider = mock( ClusterCompositionProvider.class ); - when( provider.getClusterComposition( any( CompletionStage.class ), any( String.class ) ) ).then( invocation -> + when( provider.getClusterComposition( any( Connection.class ), any( String.class ), any( InternalBookmark.class ) ) ).then( invocation -> { - CompletionStage connectionStage = invocation.getArgument( 0 ); - BoltServerAddress address = await( connectionStage ).serverAddress(); + Connection connection = invocation.getArgument( 0 ); + BoltServerAddress address = connection.serverAddress(); Object response = responsesByAddress.get( address ); assertNotNull( response ); if ( response instanceof Throwable ) diff --git a/driver/src/test/java/org/neo4j/driver/internal/cluster/RediscoveryUtils.java b/driver/src/test/java/org/neo4j/driver/internal/cluster/RediscoveryUtils.java new file mode 100644 index 0000000000..d19bc849e1 --- /dev/null +++ b/driver/src/test/java/org/neo4j/driver/internal/cluster/RediscoveryUtils.java @@ -0,0 +1,39 @@ +/* + * 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.cluster; + +import org.neo4j.driver.AccessMode; +import org.neo4j.driver.internal.InternalBookmark; +import org.neo4j.driver.internal.async.ConnectionContext; +import org.neo4j.driver.internal.async.ImmutableConnectionContext; + +import static org.neo4j.driver.internal.messaging.request.MultiDatabaseUtil.ABSENT_DB_NAME; + +public class RediscoveryUtils +{ + public static ConnectionContext contextWithDatabase( String databaseName ) + { + return new ImmutableConnectionContext( databaseName, InternalBookmark.empty(), AccessMode.WRITE ); + } + + public static ConnectionContext contextWithMode( AccessMode mode ) + { + return new ImmutableConnectionContext( ABSENT_DB_NAME, InternalBookmark.empty(), mode ); + } +} diff --git a/driver/src/test/java/org/neo4j/driver/internal/cluster/RoutingProcedureClusterCompositionProviderTest.java b/driver/src/test/java/org/neo4j/driver/internal/cluster/RoutingProcedureClusterCompositionProviderTest.java index 426fd528ec..efb2c90b8c 100644 --- a/driver/src/test/java/org/neo4j/driver/internal/cluster/RoutingProcedureClusterCompositionProviderTest.java +++ b/driver/src/test/java/org/neo4j/driver/internal/cluster/RoutingProcedureClusterCompositionProviderTest.java @@ -24,7 +24,6 @@ import java.util.HashSet; import java.util.Map; import java.util.Set; -import java.util.concurrent.CompletionStage; import org.neo4j.driver.Record; import org.neo4j.driver.Statement; @@ -32,9 +31,11 @@ import org.neo4j.driver.exceptions.ProtocolException; import org.neo4j.driver.exceptions.ServiceUnavailableException; import org.neo4j.driver.internal.BoltServerAddress; +import org.neo4j.driver.internal.InternalBookmark; import org.neo4j.driver.internal.InternalRecord; import org.neo4j.driver.internal.spi.Connection; import org.neo4j.driver.internal.util.Clock; +import org.neo4j.driver.internal.util.ServerVersion; import org.neo4j.driver.internal.value.StringValue; import static java.util.Arrays.asList; @@ -46,9 +47,12 @@ 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.Values.value; +import static org.neo4j.driver.internal.InternalBookmark.empty; import static org.neo4j.driver.internal.messaging.request.MultiDatabaseUtil.ABSENT_DB_NAME; +import static org.neo4j.driver.internal.util.Futures.completedWithNull; import static org.neo4j.driver.internal.util.Futures.failedFuture; import static org.neo4j.driver.util.TestUtil.await; @@ -59,15 +63,17 @@ void shouldProtocolErrorWhenNoRecord() { // Given RoutingProcedureRunner mockedRunner = newProcedureRunnerMock(); + Connection connection = mock( Connection.class ); ClusterCompositionProvider provider = - new RoutingProcedureClusterCompositionProvider( mock( Clock.class ), mockedRunner ); + newClusterCompositionProvider( mockedRunner, connection ); - CompletionStage connectionStage = completedFuture( mock( Connection.class ) ); RoutingProcedureResponse noRecordsResponse = newRoutingResponse(); - when( mockedRunner.run( eq( connectionStage ), any( String.class ) ) ).thenReturn( completedFuture( noRecordsResponse ) ); + when( mockedRunner.run( eq( connection ), any( String.class ), any( InternalBookmark.class ) ) ) + .thenReturn( completedFuture( noRecordsResponse ) ); // When & Then - ProtocolException error = assertThrows( ProtocolException.class, () -> await( provider.getClusterComposition( connectionStage, ABSENT_DB_NAME ) ) ); + ProtocolException error = assertThrows( ProtocolException.class, + () -> await( provider.getClusterComposition( connection, ABSENT_DB_NAME, empty() ) ) ); assertThat( error.getMessage(), containsString( "records received '0' is too few or too many." ) ); } @@ -76,16 +82,17 @@ void shouldProtocolErrorWhenMoreThanOneRecord() { // Given RoutingProcedureRunner mockedRunner = newProcedureRunnerMock(); + Connection connection = mock( Connection.class ); ClusterCompositionProvider provider = - new RoutingProcedureClusterCompositionProvider( mock( Clock.class ), mockedRunner ); + newClusterCompositionProvider( mockedRunner, connection ); - CompletionStage connectionStage = completedFuture( mock( Connection.class ) ); Record aRecord = new InternalRecord( asList( "key1", "key2" ), new Value[]{ new StringValue( "a value" ) } ); RoutingProcedureResponse routingResponse = newRoutingResponse( aRecord, aRecord ); - when( mockedRunner.run( eq( connectionStage ), any( String.class ) ) ).thenReturn( completedFuture( routingResponse ) ); + when( mockedRunner.run( eq( connection ), any( String.class ), any(InternalBookmark.class ) ) ).thenReturn( completedFuture( routingResponse ) ); // When - ProtocolException error = assertThrows( ProtocolException.class, () -> await( provider.getClusterComposition( connectionStage, ABSENT_DB_NAME ) ) ); + ProtocolException error = assertThrows( ProtocolException.class, + () -> await( provider.getClusterComposition( connection, ABSENT_DB_NAME, empty() ) ) ); assertThat( error.getMessage(), containsString( "records received '2' is too few or too many." ) ); } @@ -94,16 +101,17 @@ void shouldProtocolErrorWhenUnparsableRecord() { // Given RoutingProcedureRunner mockedRunner = newProcedureRunnerMock(); + Connection connection = mock( Connection.class ); ClusterCompositionProvider provider = - new RoutingProcedureClusterCompositionProvider( mock( Clock.class ), mockedRunner ); + newClusterCompositionProvider( mockedRunner, connection ); - CompletionStage connectionStage = completedFuture( mock( Connection.class ) ); Record aRecord = new InternalRecord( asList( "key1", "key2" ), new Value[]{ new StringValue( "a value" ) } ); RoutingProcedureResponse routingResponse = newRoutingResponse( aRecord ); - when( mockedRunner.run( eq( connectionStage ), any( String.class ) ) ).thenReturn( completedFuture( routingResponse ) ); + when( mockedRunner.run( eq( connection ), any( String.class ), any(InternalBookmark.class ) ) ).thenReturn( completedFuture( routingResponse ) ); // When - ProtocolException error = assertThrows( ProtocolException.class, () -> await( provider.getClusterComposition( connectionStage, ABSENT_DB_NAME ) ) ); + ProtocolException error = assertThrows( ProtocolException.class, + () -> await( provider.getClusterComposition( connection, ABSENT_DB_NAME, empty() ) ) ); assertThat( error.getMessage(), containsString( "unparsable record received." ) ); } @@ -111,23 +119,24 @@ void shouldProtocolErrorWhenUnparsableRecord() void shouldProtocolErrorWhenNoRouters() { // Given - RoutingProcedureRunner mockedRunner = newProcedureRunnerMock(); + MultiDatabasesRoutingProcedureRunner mockedRunner = newMultiDBProcedureRunnerMock(); + Connection connection = mock( Connection.class ); Clock mockedClock = mock( Clock.class ); ClusterCompositionProvider provider = - new RoutingProcedureClusterCompositionProvider( mockedClock, mockedRunner ); + newClusterCompositionProvider( mockedRunner, connection, mockedClock ); - CompletionStage connectionStage = completedFuture( mock( Connection.class ) ); Record record = new InternalRecord( asList( "ttl", "servers" ), new Value[]{ value( 100 ), value( asList( serverInfo( "READ", "one:1337", "two:1337" ), serverInfo( "WRITE", "one:1337" ) ) ) } ); RoutingProcedureResponse routingResponse = newRoutingResponse( record ); - when( mockedRunner.run( eq( connectionStage ), any( String.class ) ) ).thenReturn( completedFuture( routingResponse ) ); + when( mockedRunner.run( eq( connection ), any( String.class ), any(InternalBookmark.class ) ) ).thenReturn( completedFuture( routingResponse ) ); when( mockedClock.millis() ).thenReturn( 12345L ); // When - ProtocolException error = assertThrows( ProtocolException.class, () -> await( provider.getClusterComposition( connectionStage, ABSENT_DB_NAME ) ) ); + ProtocolException error = assertThrows( ProtocolException.class, + () -> await( provider.getClusterComposition( connection, ABSENT_DB_NAME, empty() ) ) ); assertThat( error.getMessage(), containsString( "no router or reader found in response." ) ); } @@ -135,23 +144,24 @@ void shouldProtocolErrorWhenNoRouters() void shouldProtocolErrorWhenNoReaders() { // Given - RoutingProcedureRunner mockedRunner = newProcedureRunnerMock(); + MultiDatabasesRoutingProcedureRunner mockedRunner = newMultiDBProcedureRunnerMock(); + Connection connection = mock( Connection.class ); Clock mockedClock = mock( Clock.class ); ClusterCompositionProvider provider = - new RoutingProcedureClusterCompositionProvider( mockedClock, mockedRunner ); + newClusterCompositionProvider( mockedRunner, connection, mockedClock ); - CompletionStage connectionStage = completedFuture( mock( Connection.class ) ); Record record = new InternalRecord( asList( "ttl", "servers" ), new Value[]{ value( 100 ), value( asList( serverInfo( "WRITE", "one:1337" ), serverInfo( "ROUTE", "one:1337", "two:1337" ) ) ) } ); RoutingProcedureResponse routingResponse = newRoutingResponse( record ); - when( mockedRunner.run( eq( connectionStage ), any( String.class ) ) ).thenReturn( completedFuture( routingResponse ) ); + when( mockedRunner.run( eq( connection ), any( String.class ), any(InternalBookmark.class ) ) ).thenReturn( completedFuture( routingResponse ) ); when( mockedClock.millis() ).thenReturn( 12345L ); // When - ProtocolException error = assertThrows( ProtocolException.class, () -> await( provider.getClusterComposition( connectionStage, ABSENT_DB_NAME ) ) ); + ProtocolException error = assertThrows( ProtocolException.class, + () -> await( provider.getClusterComposition( connection, ABSENT_DB_NAME, empty() ) ) ); assertThat( error.getMessage(), containsString( "no router or reader found in response." ) ); } @@ -160,15 +170,16 @@ void shouldPropagateConnectionFailureExceptions() { // Given RoutingProcedureRunner mockedRunner = newProcedureRunnerMock(); + Connection connection = mock( Connection.class ); ClusterCompositionProvider provider = - new RoutingProcedureClusterCompositionProvider( mock( Clock.class ), mockedRunner ); + newClusterCompositionProvider( mockedRunner, connection ); - CompletionStage connectionStage = completedFuture( mock( Connection.class ) ); - when( mockedRunner.run( eq( connectionStage ), any( String.class ) ) ).thenReturn( failedFuture( + when( mockedRunner.run( eq( connection ), any( String.class ), any(InternalBookmark.class ) ) ).thenReturn( failedFuture( new ServiceUnavailableException( "Connection breaks during cypher execution" ) ) ); // When & Then - ServiceUnavailableException e = assertThrows( ServiceUnavailableException.class, () -> await( provider.getClusterComposition( connectionStage, ABSENT_DB_NAME ) ) ); + ServiceUnavailableException e = assertThrows( ServiceUnavailableException.class, + () -> await( provider.getClusterComposition( connection, ABSENT_DB_NAME, empty() ) ) ); assertThat( e.getMessage(), containsString( "Connection breaks during cypher execution" ) ); } @@ -177,11 +188,11 @@ void shouldReturnSuccessResultWhenNoError() { // Given Clock mockedClock = mock( Clock.class ); - RoutingProcedureRunner mockedRunner = newProcedureRunnerMock(); + Connection connection = mock( Connection.class ); + MultiDatabasesRoutingProcedureRunner mockedRunner = newMultiDBProcedureRunnerMock(); ClusterCompositionProvider provider = - new RoutingProcedureClusterCompositionProvider( mockedClock, mockedRunner ); + newClusterCompositionProvider( mockedRunner, connection, mockedClock ); - CompletionStage connectionStage = completedFuture( mock( Connection.class ) ); Record record = new InternalRecord( asList( "ttl", "servers" ), new Value[]{ value( 100 ), value( asList( serverInfo( "READ", "one:1337", "two:1337" ), @@ -189,11 +200,12 @@ void shouldReturnSuccessResultWhenNoError() serverInfo( "ROUTE", "one:1337", "two:1337" ) ) ) } ); RoutingProcedureResponse routingResponse = newRoutingResponse( record ); - when( mockedRunner.run( eq( connectionStage ), any( String.class ) ) ).thenReturn( completedFuture( routingResponse ) ); + when( mockedRunner.run( eq( connection ), any( String.class ), any(InternalBookmark.class ) ) ) + .thenReturn( completedFuture( routingResponse ) ); when( mockedClock.millis() ).thenReturn( 12345L ); // When - ClusterComposition cluster = await( provider.getClusterComposition( connectionStage, ABSENT_DB_NAME ) ); + ClusterComposition cluster = await( provider.getClusterComposition( connection, ABSENT_DB_NAME, empty() ) ); // Then assertEquals( 12345 + 100_000, cluster.expirationTimestamp() ); @@ -206,20 +218,50 @@ void shouldReturnSuccessResultWhenNoError() void shouldReturnFailureWhenProcedureRunnerFails() { RoutingProcedureRunner procedureRunner = newProcedureRunnerMock(); - CompletionStage connectionStage = completedFuture( mock( Connection.class ) ); + Connection connection = mock( Connection.class ); RuntimeException error = new RuntimeException( "hi" ); - when( procedureRunner.run( eq( connectionStage ), any( String.class ) ) ) + when( procedureRunner.run( eq( connection ), any( String.class ), any(InternalBookmark.class ) ) ) .thenReturn( completedFuture( newRoutingResponse( error ) ) ); RoutingProcedureClusterCompositionProvider provider = - new RoutingProcedureClusterCompositionProvider( mock( Clock.class ), procedureRunner ); + newClusterCompositionProvider( procedureRunner, connection ); RuntimeException e = assertThrows( RuntimeException.class, - () -> await( provider.getClusterComposition( connectionStage, ABSENT_DB_NAME ) ) ); + () -> await( provider.getClusterComposition( connection, ABSENT_DB_NAME, empty() ) ) ); assertEquals( error, e ); } + @Test + void shouldUseMultiDBProcedureRunnerWhenConnectingWith40Server() throws Throwable + { + MultiDatabasesRoutingProcedureRunner procedureRunner = newMultiDBProcedureRunnerMock(); + Connection connection = mock( Connection.class ); + + RoutingProcedureClusterCompositionProvider provider = + newClusterCompositionProvider( procedureRunner, connection ); + + when( procedureRunner.run( eq( connection ), any( String.class ), any(InternalBookmark.class ) ) ).thenReturn( completedWithNull() ); + provider.getClusterComposition( connection, ABSENT_DB_NAME, empty() ); + + verify( procedureRunner ).run( eq( connection ), any( String.class ), any( InternalBookmark.class ) ); + } + + @Test + void shouldUseProcedureRunnerWhenConnectingWith35AndPreviousServers() throws Throwable + { + RoutingProcedureRunner procedureRunner = newProcedureRunnerMock(); + Connection connection = mock( Connection.class ); + + RoutingProcedureClusterCompositionProvider provider = + newClusterCompositionProvider( procedureRunner, connection ); + + when( procedureRunner.run( eq( connection ), any( String.class ), any(InternalBookmark.class ) ) ).thenReturn( completedWithNull() ); + provider.getClusterComposition( connection, ABSENT_DB_NAME, empty() ); + + verify( procedureRunner ).run( eq( connection ), any( String.class ), any( InternalBookmark.class ) ); + } + private static Map serverInfo( String role, String... addresses ) { Map map = new HashMap<>(); @@ -243,6 +285,11 @@ private static RoutingProcedureRunner newProcedureRunnerMock() return mock( RoutingProcedureRunner.class ); } + private static MultiDatabasesRoutingProcedureRunner newMultiDBProcedureRunnerMock() + { + return mock( MultiDatabasesRoutingProcedureRunner.class ); + } + private static RoutingProcedureResponse newRoutingResponse( Record... records ) { return new RoutingProcedureResponse( new Statement( "procedure" ), asList( records ) ); @@ -252,4 +299,22 @@ private static RoutingProcedureResponse newRoutingResponse( Throwable error ) { return new RoutingProcedureResponse( new Statement( "procedure" ), error ); } + + private static RoutingProcedureClusterCompositionProvider newClusterCompositionProvider( RoutingProcedureRunner runner, Connection connection ) + { + when( connection.serverVersion() ).thenReturn( ServerVersion.v3_5_0 ); + return new RoutingProcedureClusterCompositionProvider( mock( Clock.class ), runner, newMultiDBProcedureRunnerMock() ); + } + + private static RoutingProcedureClusterCompositionProvider newClusterCompositionProvider( MultiDatabasesRoutingProcedureRunner runner, Connection connection ) + { + when( connection.serverVersion() ).thenReturn( ServerVersion.v4_0_0 ); + return new RoutingProcedureClusterCompositionProvider( mock( Clock.class ), newProcedureRunnerMock(), runner ); + } + + private static RoutingProcedureClusterCompositionProvider newClusterCompositionProvider( MultiDatabasesRoutingProcedureRunner runner, Connection connection, Clock clock ) + { + when( connection.serverVersion() ).thenReturn( ServerVersion.v4_0_0 ); + return new RoutingProcedureClusterCompositionProvider( clock, newProcedureRunnerMock(), runner ); + } } diff --git a/driver/src/test/java/org/neo4j/driver/internal/cluster/RoutingProcedureRunnerTest.java b/driver/src/test/java/org/neo4j/driver/internal/cluster/RoutingProcedureRunnerTest.java index 524dd4e7dd..a2b0a07e5d 100644 --- a/driver/src/test/java/org/neo4j/driver/internal/cluster/RoutingProcedureRunnerTest.java +++ b/driver/src/test/java/org/neo4j/driver/internal/cluster/RoutingProcedureRunnerTest.java @@ -20,92 +20,56 @@ import org.junit.jupiter.api.Test; import org.junit.jupiter.params.ParameterizedTest; -import org.junit.jupiter.params.provider.ValueSource; +import org.junit.jupiter.params.provider.MethodSource; import java.net.URI; import java.util.List; import java.util.Map; -import java.util.Objects; import java.util.concurrent.CompletionStage; +import java.util.stream.Stream; +import org.neo4j.driver.AccessMode; import org.neo4j.driver.Record; import org.neo4j.driver.Statement; import org.neo4j.driver.Value; -import org.neo4j.driver.exceptions.ClientException; -import org.neo4j.driver.internal.BoltServerAddress; +import org.neo4j.driver.exceptions.FatalDiscoveryException; +import org.neo4j.driver.internal.BookmarkHolder; import org.neo4j.driver.internal.spi.Connection; -import static java.util.Arrays.asList; import static java.util.Collections.EMPTY_MAP; import static java.util.Collections.singletonList; import static java.util.concurrent.CompletableFuture.completedFuture; +import static org.hamcrest.Matchers.equalTo; +import static org.hamcrest.junit.MatcherAssert.assertThat; import static org.junit.jupiter.api.Assertions.assertEquals; -import static org.junit.jupiter.api.Assertions.assertFalse; 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.verify; -import static org.mockito.Mockito.when; import static org.neo4j.driver.Values.parameters; -import static org.neo4j.driver.internal.cluster.RoutingProcedureRunner.DATABASE_NAME; +import static org.neo4j.driver.internal.InternalBookmark.empty; import static org.neo4j.driver.internal.cluster.RoutingProcedureRunner.GET_ROUTING_TABLE; -import static org.neo4j.driver.internal.cluster.RoutingProcedureRunner.MULTI_DB_GET_ROUTING_TABLE; import static org.neo4j.driver.internal.cluster.RoutingProcedureRunner.ROUTING_CONTEXT; import static org.neo4j.driver.internal.messaging.request.MultiDatabaseUtil.ABSENT_DB_NAME; import static org.neo4j.driver.internal.messaging.request.MultiDatabaseUtil.SYSTEM_DB_NAME; -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.ServerVersion.version; import static org.neo4j.driver.util.TestUtil.await; -class RoutingProcedureRunnerTest +class RoutingProcedureRunnerTest extends AbstractRoutingProcedureRunnerTest { - @ParameterizedTest - @ValueSource( strings = {ABSENT_DB_NAME, SYSTEM_DB_NAME, " this is a db name "} ) - void shouldCallGetRoutingTableWithEmptyMapOnSystemDatabaseForDatabase( String db ) + @Test + void shouldCallGetRoutingTableWithEmptyMap() { - RoutingProcedureRunner runner = new TestRoutingProcedureRunner( RoutingContext.EMPTY, - completedFuture( asList( mock( Record.class ), mock( Record.class ) ) ), SYSTEM_DB_NAME ); - - RoutingProcedureResponse response = await( runner.run( connectionStage( "Neo4j/4.0.0" ), db ) ); - - assertTrue( response.isSuccess() ); - assertEquals( 2, response.records().size() ); - - Value parameters = generateMultiDatabaseRoutingParameters( EMPTY_MAP, db ); - assertEquals( new Statement( "CALL " + MULTI_DB_GET_ROUTING_TABLE, parameters ), response.procedure() ); - } - - @ParameterizedTest - @ValueSource( strings = {ABSENT_DB_NAME, SYSTEM_DB_NAME, " this is a db name "} ) - void shouldCallGetRoutingTableWithParamOnSystemDatabaseForDatabase( String db ) - { - URI uri = URI.create( "neo4j://localhost/?key1=value1&key2=value2" ); - RoutingContext context = new RoutingContext( uri ); - - RoutingProcedureRunner runner = new TestRoutingProcedureRunner( context, - completedFuture( singletonList( mock( Record.class ) ) ), SYSTEM_DB_NAME ); - - RoutingProcedureResponse response = await( runner.run( connectionStage( "Neo4j/4.0.0" ), db ) ); + TestRoutingProcedureRunner runner = new TestRoutingProcedureRunner( RoutingContext.EMPTY ); + RoutingProcedureResponse response = await( runner.run( connection(), ABSENT_DB_NAME, empty() ) ); assertTrue( response.isSuccess() ); assertEquals( 1, response.records().size() ); - Value expectedParams = generateMultiDatabaseRoutingParameters( context.asMap(), db ); - assertEquals( new Statement( "CALL " + MULTI_DB_GET_ROUTING_TABLE, expectedParams ), response.procedure() ); - } - - @Test - void shouldCallGetRoutingTableWithEmptyMap() - { - RoutingProcedureRunner runner = new TestRoutingProcedureRunner( RoutingContext.EMPTY, - completedFuture( asList( mock( Record.class ), mock( Record.class ) ) ) ); - RoutingProcedureResponse response = await( runner.run( connectionStage( "Neo4j/3.2.1" ), ABSENT_DB_NAME ) ); + assertThat( runner.bookmarkHolder, equalTo( BookmarkHolder.NO_OP ) ); + assertThat( runner.connection.databaseName(), equalTo( ABSENT_DB_NAME ) ); + assertThat( runner.connection.mode(), equalTo( AccessMode.WRITE ) ); - assertTrue( response.isSuccess() ); - assertEquals( 2, response.records().size() ); - assertEquals( new Statement( "CALL " + GET_ROUTING_TABLE, parameters( ROUTING_CONTEXT, EMPTY_MAP ) ), - response.procedure() ); + Statement statement = generateRoutingStatement( EMPTY_MAP ); + assertThat( runner.procedure, equalTo( statement ) ); } @Test @@ -114,130 +78,75 @@ void shouldCallGetRoutingTableWithParam() URI uri = URI.create( "neo4j://localhost/?key1=value1&key2=value2" ); RoutingContext context = new RoutingContext( uri ); - RoutingProcedureRunner runner = new TestRoutingProcedureRunner( context, - completedFuture( singletonList( mock( Record.class ) ) ) ); - - RoutingProcedureResponse response = await( runner.run( connectionStage( "Neo4j/3.2.1" ), ABSENT_DB_NAME ) ); + TestRoutingProcedureRunner runner = new TestRoutingProcedureRunner( context ); + RoutingProcedureResponse response = await( runner.run( connection(), ABSENT_DB_NAME, empty() ) ); assertTrue( response.isSuccess() ); assertEquals( 1, response.records().size() ); - Value expectedParams = parameters( ROUTING_CONTEXT, context.asMap() ); - assertEquals( new Statement( "CALL " + GET_ROUTING_TABLE, expectedParams ), response.procedure() ); - } - - @Test - void shouldReturnFailedResponseOnClientException() - { - ClientException error = new ClientException( "Hi" ); - RoutingProcedureRunner runner = new TestRoutingProcedureRunner( RoutingContext.EMPTY, failedFuture( error ) ); - RoutingProcedureResponse response = await( runner.run( connectionStage( "Neo4j/3.2.2" ), ABSENT_DB_NAME ) ); + assertThat( runner.bookmarkHolder, equalTo( BookmarkHolder.NO_OP ) ); + assertThat( runner.connection.databaseName(), equalTo( ABSENT_DB_NAME ) ); + assertThat( runner.connection.mode(), equalTo( AccessMode.WRITE ) ); - assertFalse( response.isSuccess() ); - assertEquals( error, response.error() ); + Statement statement = generateRoutingStatement( context.asMap() ); + assertThat( response.procedure(), equalTo( statement ) ); + assertThat( runner.procedure, equalTo( statement ) ); } - @Test - void shouldReturnFailedStageOnError() + @ParameterizedTest + @MethodSource( "invalidDatabaseNames" ) + void shouldErrorWhenDatabaseIsNotAbsent( String db ) throws Throwable { - Exception error = new Exception( "Hi" ); - RoutingProcedureRunner runner = new TestRoutingProcedureRunner( RoutingContext.EMPTY, failedFuture( error ) ); - - Exception e = assertThrows( Exception.class, () -> await( runner.run( connectionStage( "Neo4j/3.2.2" ), ABSENT_DB_NAME ) ) ); - assertEquals( error, e ); + TestRoutingProcedureRunner runner = new TestRoutingProcedureRunner( RoutingContext.EMPTY ); + assertThrows( FatalDiscoveryException.class, () -> await( runner.run( connection(), db, empty() ) ) ); } - @Test - void shouldPropagateErrorFromConnectionStage() + RoutingProcedureRunner routingProcedureRunner( RoutingContext context ) { - RuntimeException error = new RuntimeException( "Hi" ); - RoutingProcedureRunner runner = new TestRoutingProcedureRunner( RoutingContext.EMPTY ); - - RuntimeException e = assertThrows( RuntimeException.class, () -> await( runner.run( failedFuture( error ), ABSENT_DB_NAME ) ) ); - assertEquals( error, e ); + return new TestRoutingProcedureRunner( context ); } - @Test - void shouldReleaseConnectionOnSuccess() + RoutingProcedureRunner routingProcedureRunner( RoutingContext context, CompletionStage> runProcedureResult ) { - RoutingProcedureRunner runner = new TestRoutingProcedureRunner( RoutingContext.EMPTY, - completedFuture( singletonList( mock( Record.class ) ) ) ); - - CompletionStage connectionStage = connectionStage( "Neo4j/3.2.2" ); - Connection connection = await( connectionStage ); - RoutingProcedureResponse response = await( runner.run( connectionStage, ABSENT_DB_NAME ) ); - - assertTrue( response.isSuccess() ); - verify( connection ).release(); + return new TestRoutingProcedureRunner( context, runProcedureResult ); } - @Test - void shouldPropagateReleaseError() + private static Stream invalidDatabaseNames() { - RoutingProcedureRunner runner = new TestRoutingProcedureRunner( RoutingContext.EMPTY, - completedFuture( singletonList( mock( Record.class ) ) ) ); - - RuntimeException releaseError = new RuntimeException( "Release failed" ); - CompletionStage connectionStage = connectionStage( "Neo4j/3.3.3", failedFuture( releaseError ) ); - Connection connection = await( connectionStage ); - - RuntimeException e = assertThrows( RuntimeException.class, () -> await( runner.run( connectionStage, ABSENT_DB_NAME ) ) ); - assertEquals( releaseError, e ); - verify( connection ).release(); + return Stream.of( SYSTEM_DB_NAME, "This is a string", null ); } - private static Value generateMultiDatabaseRoutingParameters( Map context, String db ) + private static Statement generateRoutingStatement( Map context ) { - if ( Objects.equals( ABSENT_DB_NAME, db ) ) - { - db = null; - } - return parameters( ROUTING_CONTEXT, context, DATABASE_NAME, db ); - } - - private static CompletionStage connectionStage( String serverVersion ) - { - return connectionStage( serverVersion, completedWithNull() ); - } - - private static CompletionStage connectionStage( String serverVersion, - CompletionStage releaseStage ) - { - Connection connection = mock( Connection.class ); - when( connection.serverAddress() ).thenReturn( new BoltServerAddress( "123:45" ) ); - when( connection.serverVersion() ).thenReturn( version( serverVersion ) ); - when( connection.release() ).thenReturn( releaseStage ); - return completedFuture( connection ); + Value parameters = parameters( ROUTING_CONTEXT, context ); + return new Statement( GET_ROUTING_TABLE, parameters ); } private static class TestRoutingProcedureRunner extends RoutingProcedureRunner { final CompletionStage> runProcedureResult; - final String executionDatabase; + private Connection connection; + private Statement procedure; + private BookmarkHolder bookmarkHolder; TestRoutingProcedureRunner( RoutingContext context ) { - this( context, null, ABSENT_DB_NAME ); + this( context, completedFuture( singletonList( mock( Record.class ) ) ) ); } TestRoutingProcedureRunner( RoutingContext context, CompletionStage> runProcedureResult ) - { - this( context, runProcedureResult, ABSENT_DB_NAME ); - } - - TestRoutingProcedureRunner( RoutingContext context, CompletionStage> runProcedureResult, String executionDatabase ) { super( context ); this.runProcedureResult = runProcedureResult; - this.executionDatabase = executionDatabase; } @Override - CompletionStage> runProcedure( Connection connection, Statement procedure ) + CompletionStage> runProcedure( Connection connection, Statement procedure, BookmarkHolder bookmarkHolder ) { - assertEquals( executionDatabase, connection.databaseName() ); + this.connection = connection; + this.procedure = procedure; + this.bookmarkHolder = bookmarkHolder; return runProcedureResult; } } - } diff --git a/driver/src/test/java/org/neo4j/driver/internal/cluster/RoutingTableHandlerTest.java b/driver/src/test/java/org/neo4j/driver/internal/cluster/RoutingTableHandlerTest.java index dba76deebb..90674bbda8 100644 --- a/driver/src/test/java/org/neo4j/driver/internal/cluster/RoutingTableHandlerTest.java +++ b/driver/src/test/java/org/neo4j/driver/internal/cluster/RoutingTableHandlerTest.java @@ -29,6 +29,8 @@ import org.neo4j.driver.AccessMode; import org.neo4j.driver.exceptions.ServiceUnavailableException; import org.neo4j.driver.internal.BoltServerAddress; +import org.neo4j.driver.internal.InternalBookmark; +import org.neo4j.driver.internal.async.ConnectionContext; import org.neo4j.driver.internal.spi.Connection; import org.neo4j.driver.internal.spi.ConnectionPool; import org.neo4j.driver.internal.util.FakeClock; @@ -43,6 +45,7 @@ import static org.junit.jupiter.api.Assertions.assertNotNull; 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.never; import static org.mockito.Mockito.verify; @@ -50,6 +53,8 @@ import static org.neo4j.driver.AccessMode.READ; import static org.neo4j.driver.AccessMode.WRITE; import static org.neo4j.driver.internal.BoltServerAddress.LOCAL_DEFAULT; +import static org.neo4j.driver.internal.async.ImmutableConnectionContext.simple; +import static org.neo4j.driver.internal.cluster.RediscoveryUtils.contextWithMode; import static org.neo4j.driver.internal.cluster.RoutingSettings.STALE_ROUTING_TABLE_PURGE_DELAY_MS; import static org.neo4j.driver.internal.logging.DevNullLogger.DEV_NULL_LOGGER; import static org.neo4j.driver.internal.messaging.request.MultiDatabaseUtil.ABSENT_DB_NAME; @@ -104,14 +109,14 @@ void acquireShouldUpdateRoutingTableWhenKnownRoutingTableIsStale() Set routers = new LinkedHashSet<>( singletonList( router1 ) ); ClusterComposition clusterComposition = new ClusterComposition( 42, readers, writers, routers ); Rediscovery rediscovery = mock( RediscoveryImpl.class ); - when( rediscovery.lookupClusterComposition( routingTable, connectionPool ) ) + when( rediscovery.lookupClusterComposition( eq( routingTable ), eq( connectionPool ), any() ) ) .thenReturn( completedFuture( clusterComposition ) ); RoutingTableHandler handler = newRoutingTableHandler( routingTable, rediscovery, connectionPool ); - assertNotNull( await( handler.refreshRoutingTable( READ ) ) ); + assertNotNull( await( handler.refreshRoutingTable( simple() ) ) ); - verify( rediscovery ).lookupClusterComposition( routingTable, connectionPool ); + verify( rediscovery ).lookupClusterComposition( eq ( routingTable ) , eq ( connectionPool ), any() ); assertArrayEquals( new BoltServerAddress[]{reader1, reader2}, routingTable.readers().toArray() ); assertArrayEquals( new BoltServerAddress[]{writer1}, routingTable.writers().toArray() ); assertArrayEquals( new BoltServerAddress[]{router1}, routingTable.routers().toArray() ); @@ -151,13 +156,13 @@ void shouldRetainAllFetchedAddressesInConnectionPoolAfterFetchingOfRoutingTable( ConnectionPool connectionPool = newConnectionPoolMock(); Rediscovery rediscovery = newRediscoveryMock(); - when( rediscovery.lookupClusterComposition( any(), any() ) ).thenReturn( completedFuture( + when( rediscovery.lookupClusterComposition( any(), any(), any() ) ).thenReturn( completedFuture( new ClusterComposition( 42, asOrderedSet( A, B ), asOrderedSet( B, C ), asOrderedSet( A, C ) ) ) ); RoutingTableRegistry registry = new RoutingTableRegistry() { @Override - public CompletionStage refreshRoutingTable( String databaseName, AccessMode mode ) + public CompletionStage refreshRoutingTable( ConnectionContext context ) { throw new UnsupportedOperationException(); } @@ -182,7 +187,7 @@ public void purgeAged() RoutingTableHandler handler = newRoutingTableHandler( routingTable, rediscovery, connectionPool, registry ); - RoutingTable actual = await( handler.refreshRoutingTable( READ ) ); + RoutingTable actual = await( handler.refreshRoutingTable( simple() ) ); assertEquals( routingTable, actual ); verify( connectionPool ).retainAll( new HashSet<>( asList( A, B, C ) ) ); @@ -195,14 +200,14 @@ void shouldRemoveRoutingTableHandlerIfFailedToLookup() throws Throwable RoutingTable routingTable = new ClusterRoutingTable( ABSENT_DB_NAME, new FakeClock() ); Rediscovery rediscovery = newRediscoveryMock(); - when( rediscovery.lookupClusterComposition( any(), any() ) ).thenReturn( Futures.failedFuture( new RuntimeException( "Bang!" ) ) ); + when( rediscovery.lookupClusterComposition( any(), any(), any() ) ).thenReturn( Futures.failedFuture( new RuntimeException( "Bang!" ) ) ); ConnectionPool connectionPool = newConnectionPoolMock(); RoutingTableRegistry registry = newRoutingTableRegistryMock(); // When RoutingTableHandler handler = newRoutingTableHandler( routingTable, rediscovery, connectionPool, registry ); - assertThrows( RuntimeException.class, () -> await( handler.refreshRoutingTable( READ ) ) ); + assertThrows( RuntimeException.class, () -> await( handler.refreshRoutingTable( simple() ) ) ); // Then verify( registry ).remove( ABSENT_DB_NAME ); @@ -218,11 +223,11 @@ private void testRediscoveryWhenStale( AccessMode mode ) Rediscovery rediscovery = newRediscoveryMock(); RoutingTableHandler handler = newRoutingTableHandler( routingTable, rediscovery, connectionPool ); - RoutingTable actual = await( handler.refreshRoutingTable( mode ) ); + RoutingTable actual = await( handler.refreshRoutingTable( contextWithMode( mode ) ) ); assertEquals( routingTable, actual ); verify( routingTable ).isStaleFor( mode ); - verify( rediscovery ).lookupClusterComposition( routingTable, connectionPool ); + verify( rediscovery ).lookupClusterComposition( eq( routingTable ), eq( connectionPool ), any() ); } private void testNoRediscoveryWhenNotStale( AccessMode staleMode, AccessMode notStaleMode ) @@ -236,9 +241,9 @@ private void testNoRediscoveryWhenNotStale( AccessMode staleMode, AccessMode not RoutingTableHandler handler = newRoutingTableHandler( routingTable, rediscovery, connectionPool ); - assertNotNull( await( handler.refreshRoutingTable( notStaleMode ) ) ); + assertNotNull( await( handler.refreshRoutingTable( contextWithMode( notStaleMode ) ) ) ); verify( routingTable ).isStaleFor( notStaleMode ); - verify( rediscovery, never() ).lookupClusterComposition( routingTable, connectionPool ); + verify( rediscovery, never() ).lookupClusterComposition( eq( routingTable ), eq( connectionPool ), any() ); } private static RoutingTable newStaleRoutingTableMock( AccessMode mode ) @@ -264,7 +269,7 @@ private static Rediscovery newRediscoveryMock() Rediscovery rediscovery = mock( RediscoveryImpl.class ); Set noServers = Collections.emptySet(); ClusterComposition clusterComposition = new ClusterComposition( 1, noServers, noServers, noServers ); - when( rediscovery.lookupClusterComposition( any( RoutingTable.class ), any( ConnectionPool.class ) ) ) + when( rediscovery.lookupClusterComposition( any( RoutingTable.class ), any( ConnectionPool.class ), any( InternalBookmark.class ) ) ) .thenReturn( completedFuture( clusterComposition ) ); return rediscovery; } diff --git a/driver/src/test/java/org/neo4j/driver/internal/cluster/RoutingTableRegistryImplTest.java b/driver/src/test/java/org/neo4j/driver/internal/cluster/RoutingTableRegistryImplTest.java index 65b9727926..ad0c9d6342 100644 --- a/driver/src/test/java/org/neo4j/driver/internal/cluster/RoutingTableRegistryImplTest.java +++ b/driver/src/test/java/org/neo4j/driver/internal/cluster/RoutingTableRegistryImplTest.java @@ -31,6 +31,8 @@ import org.neo4j.driver.AccessMode; import org.neo4j.driver.internal.BoltServerAddress; +import org.neo4j.driver.internal.InternalBookmark; +import org.neo4j.driver.internal.async.ImmutableConnectionContext; import org.neo4j.driver.internal.cluster.RoutingTableRegistryImpl.RoutingTableHandlerFactory; import org.neo4j.driver.internal.spi.ConnectionPool; import org.neo4j.driver.internal.util.Clock; @@ -93,7 +95,7 @@ void shouldCreateRoutingTableHandlerIfAbsentWhenFreshRoutingTable( String databa RoutingTableRegistryImpl routingTables = newRoutingTables( map, factory ); // When - routingTables.refreshRoutingTable( databaseName, AccessMode.READ ); + routingTables.refreshRoutingTable( new ImmutableConnectionContext( databaseName, InternalBookmark.empty(), AccessMode.READ ) ); // Then assertTrue( map.containsKey( databaseName ) ); @@ -111,12 +113,13 @@ void shouldReturnExistingRoutingTableHandlerWhenFreshRoutingTable( String databa RoutingTableHandlerFactory factory = mockedHandlerFactory(); RoutingTableRegistryImpl routingTables = newRoutingTables( map, factory ); + ImmutableConnectionContext context = new ImmutableConnectionContext( databaseName, InternalBookmark.empty(), AccessMode.READ ); // When - RoutingTableHandler actual = await( routingTables.refreshRoutingTable( databaseName, AccessMode.READ ) ); + RoutingTableHandler actual = await( routingTables.refreshRoutingTable( context ) ); // Then it is the one we put in map that is picked up. - verify( handler ).refreshRoutingTable( AccessMode.READ ); + verify( handler ).refreshRoutingTable( context ); // Then it is the one we put in map that is picked up. assertEquals( handler, actual ); } @@ -131,11 +134,12 @@ void shouldReturnFreshRoutingTable( AccessMode mode ) throws Throwable RoutingTableHandlerFactory factory = mockedHandlerFactory( handler ); RoutingTableRegistryImpl routingTables = new RoutingTableRegistryImpl( map, factory, DEV_NULL_LOGGER ); + ImmutableConnectionContext context = new ImmutableConnectionContext( ABSENT_DB_NAME, InternalBookmark.empty(), mode ); // When - routingTables.refreshRoutingTable( ABSENT_DB_NAME, mode ); + routingTables.refreshRoutingTable( context ); // Then - verify( handler ).refreshRoutingTable( mode ); + verify( handler ).refreshRoutingTable( context ); } @Test diff --git a/driver/src/test/java/org/neo4j/driver/internal/cluster/loadbalancing/LoadBalancerTest.java b/driver/src/test/java/org/neo4j/driver/internal/cluster/loadbalancing/LoadBalancerTest.java index 7d170a7c5a..fbe729c2cc 100644 --- a/driver/src/test/java/org/neo4j/driver/internal/cluster/loadbalancing/LoadBalancerTest.java +++ b/driver/src/test/java/org/neo4j/driver/internal/cluster/loadbalancing/LoadBalancerTest.java @@ -34,6 +34,7 @@ import org.neo4j.driver.exceptions.ServiceUnavailableException; import org.neo4j.driver.exceptions.SessionExpiredException; import org.neo4j.driver.internal.BoltServerAddress; +import org.neo4j.driver.internal.async.ConnectionContext; import org.neo4j.driver.internal.async.connection.RoutingConnection; import org.neo4j.driver.internal.cluster.AddressSet; import org.neo4j.driver.internal.cluster.ClusterComposition; @@ -64,6 +65,9 @@ import static org.mockito.Mockito.when; import static org.neo4j.driver.AccessMode.READ; import static org.neo4j.driver.AccessMode.WRITE; +import static org.neo4j.driver.internal.async.ImmutableConnectionContext.simple; +import static org.neo4j.driver.internal.cluster.RediscoveryUtils.contextWithDatabase; +import static org.neo4j.driver.internal.cluster.RediscoveryUtils.contextWithMode; import static org.neo4j.driver.internal.logging.DevNullLogger.DEV_NULL_LOGGER; import static org.neo4j.driver.internal.logging.DevNullLogging.DEV_NULL_LOGGING; import static org.neo4j.driver.internal.messaging.request.MultiDatabaseUtil.ABSENT_DB_NAME; @@ -90,7 +94,7 @@ void returnsCorrectAccessMode( AccessMode mode ) LoadBalancer loadBalancer = newLoadBalancer( connectionPool, routingTable ); - Connection acquired = await( loadBalancer.acquireConnection( ABSENT_DB_NAME, mode ) ); + Connection acquired = await( loadBalancer.acquireConnection( contextWithMode( mode ) ) ); assertThat( acquired, instanceOf( RoutingConnection.class ) ); assertThat( acquired.mode(), equalTo( mode ) ); @@ -102,13 +106,13 @@ void returnsCorrectDatabaseName( String databaseName ) { ConnectionPool connectionPool = newConnectionPoolMock(); RoutingTable routingTable = mock( RoutingTable.class ); - AddressSet readerAddresses = mock( AddressSet.class ); - when( readerAddresses.toArray() ).thenReturn( new BoltServerAddress[]{A} ); - when( routingTable.readers() ).thenReturn( readerAddresses ); + AddressSet writerAddresses = mock( AddressSet.class ); + when( writerAddresses.toArray() ).thenReturn( new BoltServerAddress[]{A} ); + when( routingTable.writers() ).thenReturn( writerAddresses ); LoadBalancer loadBalancer = newLoadBalancer( connectionPool, routingTable ); - Connection acquired = await( loadBalancer.acquireConnection( databaseName, READ ) ); + Connection acquired = await( loadBalancer.acquireConnection( contextWithDatabase( databaseName ) ) ); assertThat( acquired, instanceOf( RoutingConnection.class ) ); assertThat( acquired.databaseName(), equalTo( databaseName ) ); @@ -125,10 +129,10 @@ void shouldThrowWhenRediscoveryReturnsNoSuitableServers() LoadBalancer loadBalancer = newLoadBalancer( connectionPool, routingTable ); - SessionExpiredException error1 = assertThrows( SessionExpiredException.class, () -> await( loadBalancer.acquireConnection( ABSENT_DB_NAME, READ ) ) ); + SessionExpiredException error1 = assertThrows( SessionExpiredException.class, () -> await( loadBalancer.acquireConnection( contextWithMode( READ ) ) ) ); assertThat( error1.getMessage(), startsWith( "Failed to obtain connection towards READ server" ) ); - SessionExpiredException error2 = assertThrows( SessionExpiredException.class, () -> await( loadBalancer.acquireConnection( ABSENT_DB_NAME, WRITE ) ) ); + SessionExpiredException error2 = assertThrows( SessionExpiredException.class, () -> await( loadBalancer.acquireConnection( contextWithMode( WRITE ) ) ) ); assertThat( error2.getMessage(), startsWith( "Failed to obtain connection towards WRITE server" ) ); } @@ -152,7 +156,7 @@ void shouldSelectLeastConnectedAddress() Set seenAddresses = new HashSet<>(); for ( int i = 0; i < 10; i++ ) { - Connection connection = await( loadBalancer.acquireConnection( ABSENT_DB_NAME, READ ) ); + Connection connection = await( loadBalancer.acquireConnection( simple() ) ); seenAddresses.add( connection.serverAddress() ); } @@ -176,7 +180,7 @@ void shouldRoundRobinWhenNoActiveConnections() Set seenAddresses = new HashSet<>(); for ( int i = 0; i < 10; i++ ) { - Connection connection = await( loadBalancer.acquireConnection( ABSENT_DB_NAME, READ ) ); + Connection connection = await( loadBalancer.acquireConnection( simple() ) ); seenAddresses.add( connection.serverAddress() ); } @@ -195,7 +199,7 @@ void shouldTryMultipleServersAfterRediscovery() LoadBalancer loadBalancer = newLoadBalancer( connectionPool, routingTable ); - Connection connection = await( loadBalancer.acquireConnection( ABSENT_DB_NAME, READ ) ); + Connection connection = await( loadBalancer.acquireConnection( simple() ) ); assertNotNull( connection ); assertEquals( B, connection.serverAddress() ); @@ -232,9 +236,8 @@ private static LoadBalancer newLoadBalancer( ConnectionPool connectionPool, Rout RoutingTableRegistry routingTables = mock( RoutingTableRegistry.class ); RoutingTableHandler handler = mock( RoutingTableHandler.class ); when( handler.routingTable() ).thenReturn( routingTable ); - when( routingTables.refreshRoutingTable( any( String.class ), any( AccessMode.class ) ) ).thenReturn( CompletableFuture.completedFuture( handler ) ); + when( routingTables.refreshRoutingTable( any( ConnectionContext.class ) ) ).thenReturn( CompletableFuture.completedFuture( handler ) ); return new LoadBalancer( connectionPool, routingTables, DEV_NULL_LOGGER, new LeastConnectedLoadBalancingStrategy( connectionPool, DEV_NULL_LOGGING ), GlobalEventExecutor.INSTANCE ); } - } diff --git a/driver/src/test/java/org/neo4j/driver/internal/cluster/loadbalancing/RoutingTableAndConnectionPoolTest.java b/driver/src/test/java/org/neo4j/driver/internal/cluster/loadbalancing/RoutingTableAndConnectionPoolTest.java index 237b084cf6..0d3adaa193 100644 --- a/driver/src/test/java/org/neo4j/driver/internal/cluster/loadbalancing/RoutingTableAndConnectionPoolTest.java +++ b/driver/src/test/java/org/neo4j/driver/internal/cluster/loadbalancing/RoutingTableAndConnectionPoolTest.java @@ -43,11 +43,11 @@ import java.util.concurrent.Future; import java.util.concurrent.TimeUnit; -import org.neo4j.driver.AccessMode; import org.neo4j.driver.Logging; import org.neo4j.driver.exceptions.FatalDiscoveryException; import org.neo4j.driver.exceptions.ProtocolException; import org.neo4j.driver.internal.BoltServerAddress; +import org.neo4j.driver.internal.InternalBookmark; import org.neo4j.driver.internal.async.connection.BootstrapFactory; import org.neo4j.driver.internal.async.connection.ChannelConnector; import org.neo4j.driver.internal.async.pool.ConnectionFactory; @@ -81,6 +81,7 @@ import static org.mockito.Mockito.when; import static org.neo4j.driver.Logging.none; import static org.neo4j.driver.internal.async.connection.ChannelAttributes.setServerAddress; +import static org.neo4j.driver.internal.cluster.RediscoveryUtils.contextWithDatabase; import static org.neo4j.driver.internal.cluster.RoutingSettings.STALE_ROUTING_TABLE_PURGE_DELAY_MS; import static org.neo4j.driver.internal.messaging.request.MultiDatabaseUtil.ABSENT_DB_NAME; import static org.neo4j.driver.internal.messaging.request.MultiDatabaseUtil.SYSTEM_DB_NAME; @@ -109,12 +110,12 @@ void shouldAddServerToRoutingTableAndConnectionPool() throws Throwable // Given ConnectionPool connectionPool = newConnectionPool(); Rediscovery rediscovery = mock( Rediscovery.class ); - when( rediscovery.lookupClusterComposition( any(), any() ) ).thenReturn( clusterComposition( A ) ); + when( rediscovery.lookupClusterComposition( any(), any(), any() ) ).thenReturn( clusterComposition( A ) ); RoutingTableRegistryImpl routingTables = newRoutingTables( connectionPool, rediscovery ); LoadBalancer loadBalancer = newLoadBalancer( connectionPool, routingTables ); // When - await( loadBalancer.acquireConnection( "neo4j", AccessMode.WRITE ) ); + await( loadBalancer.acquireConnection( contextWithDatabase( "neo4j" ) ) ); // Then assertThat( routingTables.allServers().size(), equalTo( 1 ) ); @@ -129,12 +130,12 @@ void shouldNotAddToRoutingTableWhenFailedWithRoutingError() throws Throwable // Given ConnectionPool connectionPool = newConnectionPool(); Rediscovery rediscovery = mock( Rediscovery.class ); - when( rediscovery.lookupClusterComposition( any(), any() ) ).thenReturn( Futures.failedFuture( new FatalDiscoveryException( "No database found" ) ) ); + when( rediscovery.lookupClusterComposition( any(), any(), any() ) ).thenReturn( Futures.failedFuture( new FatalDiscoveryException( "No database found" ) ) ); RoutingTableRegistryImpl routingTables = newRoutingTables( connectionPool, rediscovery ); LoadBalancer loadBalancer = newLoadBalancer( connectionPool, routingTables ); // When - assertThrows( FatalDiscoveryException.class, () -> await( loadBalancer.acquireConnection( "neo4j", AccessMode.WRITE ) ) ); + assertThrows( FatalDiscoveryException.class, () -> await( loadBalancer.acquireConnection( contextWithDatabase( "neo4j" ) ) ) ); // Then assertTrue( routingTables.allServers().isEmpty() ); @@ -148,12 +149,12 @@ void shouldNotAddToRoutingTableWhenFailedWithProtocolError() throws Throwable // Given ConnectionPool connectionPool = newConnectionPool(); Rediscovery rediscovery = mock( Rediscovery.class ); - when( rediscovery.lookupClusterComposition( any(), any() ) ).thenReturn( Futures.failedFuture( new ProtocolException( "No database found" ) ) ); + when( rediscovery.lookupClusterComposition( any(), any(), any() ) ).thenReturn( Futures.failedFuture( new ProtocolException( "No database found" ) ) ); RoutingTableRegistryImpl routingTables = newRoutingTables( connectionPool, rediscovery ); LoadBalancer loadBalancer = newLoadBalancer( connectionPool, routingTables ); // When - assertThrows( ProtocolException.class, () -> await( loadBalancer.acquireConnection( "neo4j", AccessMode.WRITE ) ) ); + assertThrows( ProtocolException.class, () -> await( loadBalancer.acquireConnection( contextWithDatabase( "neo4j" ) ) ) ); // Then assertTrue( routingTables.allServers().isEmpty() ); @@ -167,12 +168,12 @@ void shouldNotAddToRoutingTableWhenFailedWithSecurityError() throws Throwable // Given ConnectionPool connectionPool = newConnectionPool(); Rediscovery rediscovery = mock( Rediscovery.class ); - when( rediscovery.lookupClusterComposition( any(), any() ) ).thenReturn( Futures.failedFuture( new SecurityException( "No database found" ) ) ); + when( rediscovery.lookupClusterComposition( any(), any(), any() ) ).thenReturn( Futures.failedFuture( new SecurityException( "No database found" ) ) ); RoutingTableRegistryImpl routingTables = newRoutingTables( connectionPool, rediscovery ); LoadBalancer loadBalancer = newLoadBalancer( connectionPool, routingTables ); // When - assertThrows( SecurityException.class, () -> await( loadBalancer.acquireConnection( "neo4j", AccessMode.WRITE ) ) ); + assertThrows( SecurityException.class, () -> await( loadBalancer.acquireConnection( contextWithDatabase( "neo4j" ) ) ) ); // Then assertTrue( routingTables.allServers().isEmpty() ); @@ -186,12 +187,12 @@ void shouldNotRemoveNewlyAddedRoutingTableEvenIfItIsExpired() throws Throwable // Given ConnectionPool connectionPool = newConnectionPool(); Rediscovery rediscovery = mock( Rediscovery.class ); - when( rediscovery.lookupClusterComposition( any(), any() ) ).thenReturn( expiredClusterComposition( A ) ); + when( rediscovery.lookupClusterComposition( any(), any(), any() ) ).thenReturn( expiredClusterComposition( A ) ); RoutingTableRegistryImpl routingTables = newRoutingTables( connectionPool, rediscovery ); LoadBalancer loadBalancer = newLoadBalancer( connectionPool, routingTables ); // When - Connection connection = await( loadBalancer.acquireConnection( "neo4j", AccessMode.WRITE ) ); + Connection connection = await( loadBalancer.acquireConnection( contextWithDatabase( "neo4j" ) ) ); await( connection.release() ); // Then @@ -209,14 +210,14 @@ void shouldRemoveExpiredRoutingTableAndServers() throws Throwable // Given ConnectionPool connectionPool = newConnectionPool(); Rediscovery rediscovery = mock( Rediscovery.class ); - when( rediscovery.lookupClusterComposition( any(), any() ) ).thenReturn( expiredClusterComposition( A ) ).thenReturn( clusterComposition( B ) ); + when( rediscovery.lookupClusterComposition( any(), any(), any() ) ).thenReturn( expiredClusterComposition( A ) ).thenReturn( clusterComposition( B ) ); RoutingTableRegistryImpl routingTables = newRoutingTables( connectionPool, rediscovery ); LoadBalancer loadBalancer = newLoadBalancer( connectionPool, routingTables ); // When - Connection connection = await( loadBalancer.acquireConnection( "neo4j", AccessMode.WRITE ) ); + Connection connection = await( loadBalancer.acquireConnection( contextWithDatabase( "neo4j" ) ) ); await( connection.release() ); - await( loadBalancer.acquireConnection( "foo", AccessMode.WRITE ) ); + await( loadBalancer.acquireConnection( contextWithDatabase( "foo" ) ) ); // Then assertFalse( routingTables.contains( "neo4j" ) ); @@ -234,13 +235,13 @@ void shouldRemoveExpiredRoutingTableButNotServer() throws Throwable // Given ConnectionPool connectionPool = newConnectionPool(); Rediscovery rediscovery = mock( Rediscovery.class ); - when( rediscovery.lookupClusterComposition( any(), any() ) ).thenReturn( expiredClusterComposition( A ) ).thenReturn( clusterComposition( B ) ); + when( rediscovery.lookupClusterComposition( any(), any(), any() ) ).thenReturn( expiredClusterComposition( A ) ).thenReturn( clusterComposition( B ) ); RoutingTableRegistryImpl routingTables = newRoutingTables( connectionPool, rediscovery ); LoadBalancer loadBalancer = newLoadBalancer( connectionPool, routingTables ); // When - await( loadBalancer.acquireConnection( "neo4j", AccessMode.WRITE ) ); - await( loadBalancer.acquireConnection( "foo", AccessMode.WRITE ) ); + await( loadBalancer.acquireConnection( contextWithDatabase("neo4j" ) ) ); + await( loadBalancer.acquireConnection( contextWithDatabase( "foo" ) ) ); // Then assertThat( routingTables.allServers().size(), equalTo( 1 ) ); @@ -293,7 +294,7 @@ private void acquireAndReleaseConnections( LoadBalancer loadBalancer ) throws In { Future future = executorService.submit( () -> { int index = random.nextInt( DATABASES.length ); - CompletionStage task = loadBalancer.acquireConnection( DATABASES[index], AccessMode.WRITE ).thenCompose( Connection::release ); + CompletionStage task = loadBalancer.acquireConnection( contextWithDatabase( DATABASES[index] ) ).thenCompose( Connection::release ); await( task ); } ); futures[i] = future; @@ -373,7 +374,7 @@ private CompletableFuture clusterComposition( long expireAft private class RandomizedRediscovery implements Rediscovery { @Override - public CompletionStage lookupClusterComposition( RoutingTable routingTable, ConnectionPool connectionPool ) + public CompletionStage lookupClusterComposition( RoutingTable routingTable, ConnectionPool connectionPool, InternalBookmark bookmark ) { // when looking up a new routing table, we return a valid random routing table back Set servers = new HashSet<>(); diff --git a/driver/src/test/java/org/neo4j/driver/internal/handlers/CommitTxResponseHandlerTest.java b/driver/src/test/java/org/neo4j/driver/internal/handlers/CommitTxResponseHandlerTest.java index 594cf04aae..ff051c875c 100644 --- a/driver/src/test/java/org/neo4j/driver/internal/handlers/CommitTxResponseHandlerTest.java +++ b/driver/src/test/java/org/neo4j/driver/internal/handlers/CommitTxResponseHandlerTest.java @@ -22,8 +22,8 @@ import java.util.concurrent.CompletableFuture; -import org.neo4j.driver.internal.Bookmarks; import org.neo4j.driver.Value; +import org.neo4j.driver.internal.InternalBookmark; import static java.util.Collections.emptyMap; import static java.util.Collections.singletonMap; @@ -35,7 +35,7 @@ class CommitTxResponseHandlerTest { - private final CompletableFuture future = new CompletableFuture<>(); + private final CompletableFuture future = new CompletableFuture<>(); private final CommitTxResponseHandler handler = new CommitTxResponseHandler( future ); @Test @@ -53,7 +53,7 @@ void shouldHandleSuccessWithBookmark() handler.onSuccess( singletonMap( "bookmark", value( bookmarkString ) ) ); - assertEquals( Bookmarks.from( bookmarkString ), await( future ) ); + assertEquals( InternalBookmark.parse( bookmarkString ), await( future ) ); } @Test diff --git a/driver/src/test/java/org/neo4j/driver/internal/handlers/SessionPullAllResponseHandlerTest.java b/driver/src/test/java/org/neo4j/driver/internal/handlers/SessionPullAllResponseHandlerTest.java index c3552c460e..28eb9224d8 100644 --- a/driver/src/test/java/org/neo4j/driver/internal/handlers/SessionPullAllResponseHandlerTest.java +++ b/driver/src/test/java/org/neo4j/driver/internal/handlers/SessionPullAllResponseHandlerTest.java @@ -23,8 +23,8 @@ import java.util.concurrent.CompletableFuture; import org.neo4j.driver.internal.BoltServerAddress; -import org.neo4j.driver.internal.Bookmarks; -import org.neo4j.driver.internal.BookmarksHolder; +import org.neo4j.driver.internal.BookmarkHolder; +import org.neo4j.driver.internal.InternalBookmark; import org.neo4j.driver.internal.spi.Connection; import org.neo4j.driver.internal.util.ServerVersion; import org.neo4j.driver.Statement; @@ -65,23 +65,23 @@ void shouldReleaseConnectionOnFailure() void shouldUpdateBookmarksOnSuccess() { String bookmarkValue = "neo4j:bookmark:v1:tx42"; - BookmarksHolder bookmarksHolder = mock( BookmarksHolder.class ); - SessionPullAllResponseHandler handler = newHandler( newConnectionMock(), bookmarksHolder ); + BookmarkHolder bookmarkHolder = mock( BookmarkHolder.class ); + SessionPullAllResponseHandler handler = newHandler( newConnectionMock(), bookmarkHolder ); handler.onSuccess( singletonMap( "bookmark", value( bookmarkValue ) ) ); - verify( bookmarksHolder ).setBookmarks( Bookmarks.from( bookmarkValue ) ); + verify( bookmarkHolder ).setBookmark( InternalBookmark.parse( bookmarkValue ) ); } private static SessionPullAllResponseHandler newHandler( Connection connection ) { - return newHandler( connection, BookmarksHolder.NO_OP ); + return newHandler( connection, BookmarkHolder.NO_OP ); } - private static SessionPullAllResponseHandler newHandler( Connection connection, BookmarksHolder bookmarksHolder ) + private static SessionPullAllResponseHandler newHandler( Connection connection, BookmarkHolder bookmarkHolder ) { RunResponseHandler runHandler = new RunResponseHandler( new CompletableFuture<>(), METADATA_EXTRACTOR ); - return new SessionPullAllResponseHandler( new Statement( "RETURN 1" ), runHandler, connection, bookmarksHolder, METADATA_EXTRACTOR ); + return new SessionPullAllResponseHandler( new Statement( "RETURN 1" ), runHandler, connection, bookmarkHolder, METADATA_EXTRACTOR ); } private static Connection newConnectionMock() 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/SessionPullResponseHandlerTest.java index 5c0414c91a..f4b2f0d4b6 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/SessionPullResponseHandlerTest.java @@ -21,7 +21,7 @@ import java.util.Collections; import java.util.function.BiConsumer; -import org.neo4j.driver.internal.BookmarksHolder; +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.messaging.v4.BoltProtocolV4; @@ -48,8 +48,8 @@ protected void shouldHandleSuccessWithSummary( Status status ) Connection conn = mockConnection(); BiConsumer recordConsumer = mock( BiConsumer.class ); BiConsumer summaryConsumer = mock( BiConsumer.class ); - BookmarksHolder bookmarksHolder = mock( BookmarksHolder.class ); - SessionPullResponseHandler handler = newSessionResponseHandler( conn, recordConsumer, summaryConsumer, bookmarksHolder, status); + BookmarkHolder bookmarkHolder = mock( BookmarkHolder.class ); + SessionPullResponseHandler handler = newSessionResponseHandler( conn, recordConsumer, summaryConsumer, bookmarkHolder, status); // When handler.onSuccess( Collections.emptyMap() ); @@ -57,7 +57,7 @@ protected void shouldHandleSuccessWithSummary( Status status ) // Then assertThat( handler.status(), equalTo( DONE ) ); verify( conn ).release(); - verify( bookmarksHolder ).setBookmarks( any() ); + verify( bookmarkHolder ).setBookmark( any() ); verify( recordConsumer ).accept( null, null ); verify( summaryConsumer ).accept( any( ResultSummary.class ), eq( null ) ); } @@ -86,16 +86,16 @@ protected void shouldHandleFailure( Status status ) protected AbstractBasicPullResponseHandler newResponseHandlerWithStatus( Connection conn, BiConsumer recordConsumer, BiConsumer summaryConsumer, Status status ) { - BookmarksHolder bookmarksHolder = mock( BookmarksHolder.class ); - return newSessionResponseHandler( conn, recordConsumer, summaryConsumer, bookmarksHolder, status ); + BookmarkHolder bookmarkHolder = mock( BookmarkHolder.class ); + return newSessionResponseHandler( conn, recordConsumer, summaryConsumer, bookmarkHolder, status ); } private static SessionPullResponseHandler newSessionResponseHandler( Connection conn, BiConsumer recordConsumer, - BiConsumer summaryConsumer, BookmarksHolder bookmarksHolder, Status status ) + BiConsumer summaryConsumer, BookmarkHolder bookmarkHolder, Status status ) { RunResponseHandler runHandler = mock( RunResponseHandler.class ); SessionPullResponseHandler handler = - new SessionPullResponseHandler( mock( Statement.class ), runHandler, conn, bookmarksHolder, BoltProtocolV4.METADATA_EXTRACTOR ); + new SessionPullResponseHandler( mock( Statement.class ), runHandler, conn, bookmarkHolder, BoltProtocolV4.METADATA_EXTRACTOR ); handler.installRecordConsumer( recordConsumer ); handler.installSummaryConsumer( summaryConsumer ); diff --git a/driver/src/test/java/org/neo4j/driver/internal/messaging/encode/BeginMessageEncoderTest.java b/driver/src/test/java/org/neo4j/driver/internal/messaging/encode/BeginMessageEncoderTest.java index e3a8e4847f..9be907f825 100644 --- a/driver/src/test/java/org/neo4j/driver/internal/messaging/encode/BeginMessageEncoderTest.java +++ b/driver/src/test/java/org/neo4j/driver/internal/messaging/encode/BeginMessageEncoderTest.java @@ -27,7 +27,7 @@ import java.util.HashMap; import java.util.Map; -import org.neo4j.driver.internal.Bookmarks; +import org.neo4j.driver.internal.InternalBookmark; import org.neo4j.driver.internal.messaging.ValuePacker; import org.neo4j.driver.internal.messaging.request.BeginMessage; import org.neo4j.driver.AccessMode; @@ -50,7 +50,7 @@ class BeginMessageEncoderTest @EnumSource( AccessMode.class ) void shouldEncodeBeginMessage( AccessMode mode ) throws Exception { - Bookmarks bookmarks = Bookmarks.from( "neo4j:bookmark:v1:tx42" ); + InternalBookmark bookmark = InternalBookmark.parse( "neo4j:bookmark:v1:tx42" ); Map txMetadata = new HashMap<>(); txMetadata.put( "hello", value( "world" ) ); @@ -58,13 +58,13 @@ void shouldEncodeBeginMessage( AccessMode mode ) throws Exception Duration txTimeout = Duration.ofSeconds( 1 ); - encoder.encode( new BeginMessage( bookmarks, txTimeout, txMetadata, mode, ABSENT_DB_NAME ), packer ); + encoder.encode( new BeginMessage( bookmark, txTimeout, txMetadata, mode, ABSENT_DB_NAME ), packer ); InOrder order = inOrder( packer ); order.verify( packer ).packStructHeader( 1, BeginMessage.SIGNATURE ); Map expectedMetadata = new HashMap<>(); - expectedMetadata.put( "bookmarks", value( bookmarks.values() ) ); + expectedMetadata.put( "bookmarks", value( bookmark.values() ) ); expectedMetadata.put( "tx_timeout", value( 1000 ) ); expectedMetadata.put( "tx_metadata", value( txMetadata ) ); if ( mode == READ ) diff --git a/driver/src/test/java/org/neo4j/driver/internal/messaging/encode/RunWithMetadataMessageEncoderTest.java b/driver/src/test/java/org/neo4j/driver/internal/messaging/encode/RunWithMetadataMessageEncoderTest.java index c7112edf64..fd2f48446f 100644 --- a/driver/src/test/java/org/neo4j/driver/internal/messaging/encode/RunWithMetadataMessageEncoderTest.java +++ b/driver/src/test/java/org/neo4j/driver/internal/messaging/encode/RunWithMetadataMessageEncoderTest.java @@ -30,7 +30,7 @@ import org.neo4j.driver.AccessMode; import org.neo4j.driver.Statement; import org.neo4j.driver.Value; -import org.neo4j.driver.internal.Bookmarks; +import org.neo4j.driver.internal.InternalBookmark; import org.neo4j.driver.internal.messaging.ValuePacker; import org.neo4j.driver.internal.messaging.request.RunWithMetadataMessage; @@ -55,7 +55,7 @@ void shouldEncodeRunWithMetadataMessage( AccessMode mode ) throws Exception { Map params = singletonMap( "answer", value( 42 ) ); - Bookmarks bookmarks = Bookmarks.from( "neo4j:bookmark:v1:tx999" ); + InternalBookmark bookmark = InternalBookmark.parse( "neo4j:bookmark:v1:tx999" ); Map txMetadata = new HashMap<>(); txMetadata.put( "key1", value( "value1" ) ); @@ -65,7 +65,7 @@ void shouldEncodeRunWithMetadataMessage( AccessMode mode ) throws Exception Duration txTimeout = Duration.ofMillis( 42 ); Statement statement = new Statement( "RETURN $answer", value( params ) ); - encoder.encode( autoCommitTxRunMessage( statement, txTimeout, txMetadata, ABSENT_DB_NAME, mode, bookmarks ), packer ); + encoder.encode( autoCommitTxRunMessage( statement, txTimeout, txMetadata, ABSENT_DB_NAME, mode, bookmark ), packer ); InOrder order = inOrder( packer ); order.verify( packer ).packStructHeader( 3, RunWithMetadataMessage.SIGNATURE ); @@ -73,7 +73,7 @@ void shouldEncodeRunWithMetadataMessage( AccessMode mode ) throws Exception order.verify( packer ).pack( params ); Map expectedMetadata = new HashMap<>(); - expectedMetadata.put( "bookmarks", value( bookmarks.values() ) ); + expectedMetadata.put( "bookmarks", value( bookmark.values() ) ); expectedMetadata.put( "tx_timeout", value( 42 ) ); expectedMetadata.put( "tx_metadata", value( txMetadata ) ); if ( mode == READ ) diff --git a/driver/src/test/java/org/neo4j/driver/internal/messaging/request/TransactionMetadataBuilderTest.java b/driver/src/test/java/org/neo4j/driver/internal/messaging/request/TransactionMetadataBuilderTest.java index fbc8decfe4..a033b1d60e 100644 --- a/driver/src/test/java/org/neo4j/driver/internal/messaging/request/TransactionMetadataBuilderTest.java +++ b/driver/src/test/java/org/neo4j/driver/internal/messaging/request/TransactionMetadataBuilderTest.java @@ -29,7 +29,7 @@ import org.neo4j.driver.AccessMode; import org.neo4j.driver.Value; -import org.neo4j.driver.internal.Bookmarks; +import org.neo4j.driver.internal.InternalBookmark; import static java.util.Arrays.asList; import static org.junit.jupiter.api.Assertions.assertEquals; @@ -45,7 +45,7 @@ public class TransactionMetadataBuilderTest @EnumSource( AccessMode.class ) void shouldHaveCorrectMetadata( AccessMode mode ) { - Bookmarks bookmarks = Bookmarks.from( asList( "neo4j:bookmark:v1:tx11", "neo4j:bookmark:v1:tx52" ) ); + InternalBookmark bookmark = InternalBookmark.parse( asList( "neo4j:bookmark:v1:tx11", "neo4j:bookmark:v1:tx52" ) ); Map txMetadata = new HashMap<>(); txMetadata.put( "foo", value( "bar" ) ); @@ -54,10 +54,10 @@ void shouldHaveCorrectMetadata( AccessMode mode ) Duration txTimeout = Duration.ofSeconds( 7 ); - Map metadata = buildMetadata( txTimeout, txMetadata, ABSENT_DB_NAME, mode, bookmarks ); + Map metadata = buildMetadata( txTimeout, txMetadata, ABSENT_DB_NAME, mode, bookmark ); Map expectedMetadata = new HashMap<>(); - expectedMetadata.put( "bookmarks", value( bookmarks.values() ) ); + expectedMetadata.put( "bookmarks", value( bookmark.values() ) ); expectedMetadata.put( "tx_timeout", value( 7000 ) ); expectedMetadata.put( "tx_metadata", value( txMetadata ) ); if ( mode == READ ) @@ -72,7 +72,7 @@ void shouldHaveCorrectMetadata( AccessMode mode ) @ValueSource( strings = {"", "foo", "data", ABSENT_DB_NAME} ) void shouldHaveCorrectMetadataForDatabaseName( String databaseName ) { - Bookmarks bookmarks = Bookmarks.from( asList( "neo4j:bookmark:v1:tx11", "neo4j:bookmark:v1:tx52" ) ); + InternalBookmark bookmark = InternalBookmark.parse( asList( "neo4j:bookmark:v1:tx11", "neo4j:bookmark:v1:tx52" ) ); Map txMetadata = new HashMap<>(); txMetadata.put( "foo", value( "bar" ) ); @@ -81,10 +81,10 @@ void shouldHaveCorrectMetadataForDatabaseName( String databaseName ) Duration txTimeout = Duration.ofSeconds( 7 ); - Map metadata = buildMetadata( txTimeout, txMetadata, databaseName, WRITE, bookmarks ); + Map metadata = buildMetadata( txTimeout, txMetadata, databaseName, WRITE, bookmark ); Map expectedMetadata = new HashMap<>(); - expectedMetadata.put( "bookmarks", value( bookmarks.values() ) ); + expectedMetadata.put( "bookmarks", value( bookmark.values() ) ); expectedMetadata.put( "tx_timeout", value( 7000 ) ); expectedMetadata.put( "tx_metadata", value( txMetadata ) ); if ( !ABSENT_DB_NAME.equals( databaseName ) ) 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 0ebab73ccc..4270b06cb8 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 @@ -36,8 +36,8 @@ import org.neo4j.driver.TransactionConfig; import org.neo4j.driver.Value; import org.neo4j.driver.exceptions.ClientException; -import org.neo4j.driver.internal.Bookmarks; -import org.neo4j.driver.internal.BookmarksHolder; +import org.neo4j.driver.internal.BookmarkHolder; +import org.neo4j.driver.internal.InternalBookmark; import org.neo4j.driver.internal.async.ExplicitTransaction; import org.neo4j.driver.internal.async.connection.ChannelAttributes; import org.neo4j.driver.internal.async.inbound.InboundMessageDispatcher; @@ -78,6 +78,7 @@ import static org.mockito.Mockito.when; import static org.neo4j.driver.Values.value; import static org.neo4j.driver.internal.messaging.request.MultiDatabaseUtil.ABSENT_DB_NAME; +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; import static org.neo4j.driver.util.TestUtil.connectionMock; @@ -151,7 +152,7 @@ void shouldBeginTransactionWithoutBookmark() { Connection connection = connectionMock( protocol ); - CompletionStage stage = protocol.beginTransaction( connection, Bookmarks.empty(), TransactionConfig.empty() ); + CompletionStage stage = protocol.beginTransaction( connection, InternalBookmark.empty(), TransactionConfig.empty() ); verify( connection ).write( new RunMessage( "BEGIN" ), NoOpResponseHandler.INSTANCE, @@ -164,12 +165,12 @@ void shouldBeginTransactionWithoutBookmark() void shouldBeginTransactionWithBookmarks() { Connection connection = connectionMock( protocol ); - Bookmarks bookmarks = Bookmarks.from( "neo4j:bookmark:v1:tx100" ); + InternalBookmark bookmark = InternalBookmark.parse( "neo4j:bookmark:v1:tx100" ); - CompletionStage stage = protocol.beginTransaction( connection, bookmarks, TransactionConfig.empty() ); + CompletionStage stage = protocol.beginTransaction( connection, bookmark, TransactionConfig.empty() ); verify( connection ).writeAndFlush( - eq( new RunMessage( "BEGIN", bookmarks.asBeginTransactionParameters() ) ), eq( NoOpResponseHandler.INSTANCE ), + eq( new RunMessage( "BEGIN", asBeginTransactionParameters( bookmark ) ) ), eq( NoOpResponseHandler.INSTANCE ), eq( PullAllMessage.PULL_ALL ), any( BeginTxResponseHandler.class ) ); assertNull( Futures.blockingGet( stage ) ); @@ -189,13 +190,13 @@ void shouldCommitTransaction() return null; } ).when( connection ).writeAndFlush( eq( new RunMessage( "COMMIT" ) ), any(), any(), any() ); - CompletionStage stage = protocol.commitTransaction( connection ); + CompletionStage stage = protocol.commitTransaction( connection ); verify( connection ).writeAndFlush( eq( new RunMessage( "COMMIT" ) ), eq( NoOpResponseHandler.INSTANCE ), eq( PullAllMessage.PULL_ALL ), any( CommitTxResponseHandler.class ) ); - assertEquals( Bookmarks.from( bookmarkString ), await( stage ) ); + assertEquals( InternalBookmark.parse( bookmarkString ), await( stage ) ); } @Test @@ -256,7 +257,7 @@ void shouldNotSupportTransactionConfigInBeginTransaction() .withMetadata( singletonMap( "key", "value" ) ) .build(); - CompletionStage txStage = protocol.beginTransaction( connectionMock( protocol ), Bookmarks.empty(), config ); + CompletionStage txStage = protocol.beginTransaction( connectionMock( protocol ), InternalBookmark.empty(), config ); ClientException e = assertThrows( ClientException.class, () -> await( txStage ) ); assertThat( e.getMessage(), startsWith( "Driver is connected to the database that does not support transaction configuration" ) ); @@ -271,14 +272,14 @@ void shouldNotSupportTransactionConfigForAutoCommitTransactions() .build(); ClientException e = assertThrows( ClientException.class, - () -> protocol.runInAutoCommitTransaction( connectionMock( protocol ), new Statement( "RETURN 1" ), BookmarksHolder.NO_OP, config, true ) ); + () -> protocol.runInAutoCommitTransaction( connectionMock( protocol ), new Statement( "RETURN 1" ), BookmarkHolder.NO_OP, config, true ) ); assertThat( e.getMessage(), startsWith( "Driver is connected to the database that does not support transaction configuration" ) ); } @Test void shouldNotSupportDatabaseNameInBeginTransaction() { - CompletionStage txStage = protocol.beginTransaction( connectionMock( "foo", protocol ), Bookmarks.empty(), TransactionConfig.empty() ); + CompletionStage txStage = protocol.beginTransaction( connectionMock( "foo", protocol ), InternalBookmark.empty(), TransactionConfig.empty() ); ClientException e = assertThrows( ClientException.class, () -> await( txStage ) ); assertThat( e.getMessage(), startsWith( "Database name parameter for selecting database is not supported" ) ); @@ -289,7 +290,7 @@ void shouldNotSupportDatabaseNameForAutoCommitTransactions() { ClientException e = assertThrows( ClientException.class, () -> protocol.runInAutoCommitTransaction( connectionMock( "foo", protocol ), - new Statement( "RETURN 1" ), BookmarksHolder.NO_OP, TransactionConfig.empty(), true ) ); + new Statement( "RETURN 1" ), BookmarkHolder.NO_OP, TransactionConfig.empty(), true ) ); assertThat( e.getMessage(), startsWith( "Database name parameter for selecting database is not supported" ) ); } @@ -312,7 +313,7 @@ private void testRunWithoutWaitingForRunResponse( boolean autoCommitTx ) throws if ( autoCommitTx ) { cursorStage = protocol - .runInAutoCommitTransaction( connection, STATEMENT, BookmarksHolder.NO_OP, TransactionConfig.empty(), false ) + .runInAutoCommitTransaction( connection, STATEMENT, BookmarkHolder.NO_OP, TransactionConfig.empty(), false ) .asyncResult(); } else @@ -336,7 +337,7 @@ private void testRunWithWaitingForResponse( boolean success, boolean session ) t CompletionStage cursorStage; if ( session ) { - cursorStage = protocol.runInAutoCommitTransaction( connection, STATEMENT, BookmarksHolder.NO_OP, TransactionConfig.empty(), true ) + cursorStage = protocol.runInAutoCommitTransaction( connection, STATEMENT, BookmarkHolder.NO_OP, TransactionConfig.empty(), true ) .asyncResult(); } else 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 68b21d4f22..b1b908e049 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 @@ -38,9 +38,9 @@ import org.neo4j.driver.TransactionConfig; import org.neo4j.driver.Value; import org.neo4j.driver.exceptions.ClientException; -import org.neo4j.driver.internal.Bookmarks; -import org.neo4j.driver.internal.BookmarksHolder; -import org.neo4j.driver.internal.DefaultBookmarksHolder; +import org.neo4j.driver.internal.BookmarkHolder; +import org.neo4j.driver.internal.DefaultBookmarkHolder; +import org.neo4j.driver.internal.InternalBookmark; import org.neo4j.driver.internal.async.ExplicitTransaction; import org.neo4j.driver.internal.async.connection.ChannelAttributes; import org.neo4j.driver.internal.async.inbound.InboundMessageDispatcher; @@ -188,9 +188,9 @@ void shouldBeginTransactionWithoutBookmark() { Connection connection = connectionMock( protocol ); - CompletionStage stage = protocol.beginTransaction( connection, Bookmarks.empty(), TransactionConfig.empty() ); + CompletionStage stage = protocol.beginTransaction( connection, InternalBookmark.empty(), TransactionConfig.empty() ); - verify( connection ).write( new BeginMessage( Bookmarks.empty(), TransactionConfig.empty(), ABSENT_DB_NAME, WRITE ), NoOpResponseHandler.INSTANCE ); + verify( connection ).write( new BeginMessage( InternalBookmark.empty(), TransactionConfig.empty(), ABSENT_DB_NAME, WRITE ), NoOpResponseHandler.INSTANCE ); assertNull( await( stage ) ); } @@ -198,11 +198,11 @@ void shouldBeginTransactionWithoutBookmark() void shouldBeginTransactionWithBookmarks() { Connection connection = connectionMock( protocol ); - Bookmarks bookmarks = Bookmarks.from( "neo4j:bookmark:v1:tx100" ); + InternalBookmark bookmark = InternalBookmark.parse( "neo4j:bookmark:v1:tx100" ); - CompletionStage stage = protocol.beginTransaction( connection, bookmarks, TransactionConfig.empty() ); + CompletionStage stage = protocol.beginTransaction( connection, bookmark, TransactionConfig.empty() ); - verify( connection ).writeAndFlush( eq( new BeginMessage( bookmarks, TransactionConfig.empty(), ABSENT_DB_NAME, WRITE ) ), any( BeginTxResponseHandler.class ) ); + verify( connection ).writeAndFlush( eq( new BeginMessage( bookmark, TransactionConfig.empty(), ABSENT_DB_NAME, WRITE ) ), any( BeginTxResponseHandler.class ) ); assertNull( await( stage ) ); } @@ -211,9 +211,9 @@ void shouldBeginTransactionWithConfig() { Connection connection = connectionMock( protocol ); - CompletionStage stage = protocol.beginTransaction( connection, Bookmarks.empty(), txConfig ); + CompletionStage stage = protocol.beginTransaction( connection, InternalBookmark.empty(), txConfig ); - verify( connection ).write( new BeginMessage( Bookmarks.empty(), txConfig, ABSENT_DB_NAME, WRITE ), NoOpResponseHandler.INSTANCE ); + verify( connection ).write( new BeginMessage( InternalBookmark.empty(), txConfig, ABSENT_DB_NAME, WRITE ), NoOpResponseHandler.INSTANCE ); assertNull( await( stage ) ); } @@ -221,11 +221,11 @@ void shouldBeginTransactionWithConfig() void shouldBeginTransactionWithBookmarksAndConfig() { Connection connection = connectionMock( protocol ); - Bookmarks bookmarks = Bookmarks.from( "neo4j:bookmark:v1:tx4242" ); + InternalBookmark bookmark = InternalBookmark.parse( "neo4j:bookmark:v1:tx4242" ); - CompletionStage stage = protocol.beginTransaction( connection, bookmarks, txConfig ); + CompletionStage stage = protocol.beginTransaction( connection, bookmark, txConfig ); - verify( connection ).writeAndFlush( eq( new BeginMessage( bookmarks, txConfig, ABSENT_DB_NAME, WRITE ) ), any( BeginTxResponseHandler.class ) ); + verify( connection ).writeAndFlush( eq( new BeginMessage( bookmark, txConfig, ABSENT_DB_NAME, WRITE ) ), any( BeginTxResponseHandler.class ) ); assertNull( await( stage ) ); } @@ -243,10 +243,10 @@ void shouldCommitTransaction() return null; } ).when( connection ).writeAndFlush( eq( CommitMessage.COMMIT ), any() ); - CompletionStage stage = protocol.commitTransaction( connection ); + CompletionStage stage = protocol.commitTransaction( connection ); verify( connection ).writeAndFlush( eq( CommitMessage.COMMIT ), any( CommitTxResponseHandler.class ) ); - assertEquals( Bookmarks.from( bookmarkString ), await( stage ) ); + assertEquals( InternalBookmark.parse( bookmarkString ), await( stage ) ); } @Test @@ -278,28 +278,28 @@ void shouldRunInAutoCommitWithConfigTransactionWithoutWaitingForRunResponse( Acc @EnumSource( AccessMode.class ) void shouldRunInAutoCommitTransactionAndWaitForSuccessRunResponse( AccessMode mode ) throws Exception { - testSuccessfulRunInAutoCommitTxWithWaitingForResponse( Bookmarks.empty(), TransactionConfig.empty(), mode ); + testSuccessfulRunInAutoCommitTxWithWaitingForResponse( InternalBookmark.empty(), TransactionConfig.empty(), mode ); } @ParameterizedTest @EnumSource( AccessMode.class ) void shouldRunInAutoCommitTransactionWithBookmarkAndConfigAndWaitForSuccessRunResponse( AccessMode mode ) throws Exception { - testSuccessfulRunInAutoCommitTxWithWaitingForResponse( Bookmarks.from( "neo4j:bookmark:v1:tx65" ), txConfig, mode ); + testSuccessfulRunInAutoCommitTxWithWaitingForResponse( InternalBookmark.parse( "neo4j:bookmark:v1:tx65" ), txConfig, mode ); } @ParameterizedTest @EnumSource( AccessMode.class ) void shouldRunInAutoCommitTransactionAndWaitForFailureRunResponse( AccessMode mode ) throws Exception { - testFailedRunInAutoCommitTxWithWaitingForResponse( Bookmarks.empty(), TransactionConfig.empty(), mode ); + testFailedRunInAutoCommitTxWithWaitingForResponse( InternalBookmark.empty(), TransactionConfig.empty(), mode ); } @ParameterizedTest @EnumSource( AccessMode.class ) void shouldRunInAutoCommitTransactionWithBookmarkAndConfigAndWaitForFailureRunResponse( AccessMode mode ) throws Exception { - testFailedRunInAutoCommitTxWithWaitingForResponse( Bookmarks.from( "neo4j:bookmark:v1:tx163" ), txConfig, mode ); + testFailedRunInAutoCommitTxWithWaitingForResponse( InternalBookmark.parse( "neo4j:bookmark:v1:tx163" ), txConfig, mode ); } @ParameterizedTest @@ -341,12 +341,12 @@ protected void testDatabaseNameSupport( boolean autoCommitTx ) if ( autoCommitTx ) { e = assertThrows( ClientException.class, - () -> protocol.runInAutoCommitTransaction( connectionMock( "foo", protocol ), new Statement( "RETURN 1" ), BookmarksHolder.NO_OP, + () -> protocol.runInAutoCommitTransaction( connectionMock( "foo", protocol ), new Statement( "RETURN 1" ), BookmarkHolder.NO_OP, TransactionConfig.empty(), true ) ); } else { - CompletionStage txStage = protocol.beginTransaction( connectionMock( "foo", protocol ), Bookmarks.empty(), TransactionConfig.empty() ); + CompletionStage txStage = protocol.beginTransaction( connectionMock( "foo", protocol ), InternalBookmark.empty(), TransactionConfig.empty() ); e = assertThrows( ClientException.class, () -> await( txStage ) ); } @@ -361,7 +361,7 @@ protected void testRunInExplicitTransactionAndWaitForRunResponse( boolean succes CompletableFuture cursorFuture = protocol.runInExplicitTransaction( connection, STATEMENT, mock( ExplicitTransaction.class ), true ).asyncResult().toCompletableFuture(); - ResponseHandler runResponseHandler = verifyRunInvoked( connection, false, Bookmarks.empty(), TransactionConfig.empty(), mode ).runHandler; + ResponseHandler runResponseHandler = verifyRunInvoked( connection, false, InternalBookmark.empty(), TransactionConfig.empty(), mode ).runHandler; assertFalse( cursorFuture.isDone() ); if ( success ) @@ -382,13 +382,13 @@ protected void testRunInExplicitTransactionAndWaitForRunResponse( boolean succes protected void testRunWithoutWaitingForRunResponse( boolean autoCommitTx, TransactionConfig config, AccessMode mode ) throws Exception { Connection connection = connectionMock( mode, protocol ); - Bookmarks initialBookmarks = Bookmarks.from( "neo4j:bookmark:v1:tx987" ); + InternalBookmark initialBookmark = InternalBookmark.parse( "neo4j:bookmark:v1:tx987" ); CompletionStage cursorStage; if ( autoCommitTx ) { - BookmarksHolder bookmarksHolder = new DefaultBookmarksHolder( initialBookmarks ); - cursorStage = protocol.runInAutoCommitTransaction( connection, STATEMENT, bookmarksHolder, config, false ).asyncResult(); + BookmarkHolder bookmarkHolder = new DefaultBookmarkHolder( initialBookmark ); + cursorStage = protocol.runInAutoCommitTransaction( connection, STATEMENT, bookmarkHolder, config, false ).asyncResult(); } else { @@ -401,46 +401,46 @@ protected void testRunWithoutWaitingForRunResponse( boolean autoCommitTx, Transa if ( autoCommitTx ) { - verifyRunInvoked( connection, autoCommitTx, initialBookmarks, config, mode ); + verifyRunInvoked( connection, autoCommitTx, initialBookmark, config, mode ); } else { - verifyRunInvoked( connection, autoCommitTx, Bookmarks.empty(), config, mode ); + verifyRunInvoked( connection, autoCommitTx, InternalBookmark.empty(), config, mode ); } } - protected void testSuccessfulRunInAutoCommitTxWithWaitingForResponse( Bookmarks bookmarks, TransactionConfig config, AccessMode mode ) throws Exception + protected void testSuccessfulRunInAutoCommitTxWithWaitingForResponse( InternalBookmark bookmark, TransactionConfig config, AccessMode mode ) throws Exception { Connection connection = connectionMock( mode, protocol ); - BookmarksHolder bookmarksHolder = new DefaultBookmarksHolder( bookmarks ); + BookmarkHolder bookmarkHolder = new DefaultBookmarkHolder( bookmark ); CompletableFuture cursorFuture = - protocol.runInAutoCommitTransaction( connection, STATEMENT, bookmarksHolder, config, true ).asyncResult().toCompletableFuture(); + protocol.runInAutoCommitTransaction( connection, STATEMENT, bookmarkHolder, config, true ).asyncResult().toCompletableFuture(); assertFalse( cursorFuture.isDone() ); - ResponseHandlers handlers = verifyRunInvoked( connection, true, bookmarks, config, mode ); + ResponseHandlers handlers = verifyRunInvoked( connection, true, bookmark, config, mode ); String newBookmarkValue = "neo4j:bookmark:v1:tx98765"; handlers.runHandler.onSuccess( emptyMap() ); handlers.pullAllHandler.onSuccess( singletonMap( "bookmark", value( newBookmarkValue ) ) ); - assertEquals( Bookmarks.from( newBookmarkValue ), bookmarksHolder.getBookmarks() ); + assertEquals( InternalBookmark.parse( newBookmarkValue ), bookmarkHolder.getBookmark() ); assertTrue( cursorFuture.isDone() ); assertNotNull( cursorFuture.get() ); } - protected void testFailedRunInAutoCommitTxWithWaitingForResponse( Bookmarks bookmarks, TransactionConfig config, AccessMode mode ) throws Exception + protected void testFailedRunInAutoCommitTxWithWaitingForResponse( InternalBookmark bookmark, TransactionConfig config, AccessMode mode ) throws Exception { Connection connection = connectionMock( mode, protocol ); - BookmarksHolder bookmarksHolder = new DefaultBookmarksHolder( bookmarks ); + BookmarkHolder bookmarkHolder = new DefaultBookmarkHolder( bookmark ); CompletableFuture cursorFuture = - protocol.runInAutoCommitTransaction( connection, STATEMENT, bookmarksHolder, config, true ).asyncResult().toCompletableFuture(); + protocol.runInAutoCommitTransaction( connection, STATEMENT, bookmarkHolder, config, true ).asyncResult().toCompletableFuture(); assertFalse( cursorFuture.isDone() ); - ResponseHandler runResponseHandler = verifyRunInvoked( connection, true, bookmarks, config, mode ).runHandler; + ResponseHandler runResponseHandler = verifyRunInvoked( connection, true, bookmark, config, mode ).runHandler; runResponseHandler.onFailure( new RuntimeException() ); - assertEquals( bookmarks, bookmarksHolder.getBookmarks() ); + assertEquals( bookmark, bookmarkHolder.getBookmark() ); assertTrue( cursorFuture.isDone() ); assertNotNull( cursorFuture.get() ); @@ -454,7 +454,7 @@ private static Map dummyAuthToken() return authToken; } - private static ResponseHandlers verifyRunInvoked( Connection connection, boolean session, Bookmarks bookmarks, TransactionConfig config, AccessMode mode ) + private static ResponseHandlers verifyRunInvoked( Connection connection, boolean session, InternalBookmark bookmark, TransactionConfig config, AccessMode mode ) { ArgumentCaptor runHandlerCaptor = ArgumentCaptor.forClass( ResponseHandler.class ); ArgumentCaptor pullAllHandlerCaptor = ArgumentCaptor.forClass( ResponseHandler.class ); @@ -462,7 +462,7 @@ private static ResponseHandlers verifyRunInvoked( Connection connection, boolean RunWithMetadataMessage expectedMessage; if ( session ) { - expectedMessage = RunWithMetadataMessage.autoCommitTxRunMessage( STATEMENT, config, ABSENT_DB_NAME, mode, bookmarks ); + expectedMessage = RunWithMetadataMessage.autoCommitTxRunMessage( STATEMENT, config, ABSENT_DB_NAME, mode, bookmark ); } else { diff --git a/driver/src/test/java/org/neo4j/driver/internal/messaging/v3/MessageWriterV3Test.java b/driver/src/test/java/org/neo4j/driver/internal/messaging/v3/MessageWriterV3Test.java index 614b078e1d..367143c806 100644 --- a/driver/src/test/java/org/neo4j/driver/internal/messaging/v3/MessageWriterV3Test.java +++ b/driver/src/test/java/org/neo4j/driver/internal/messaging/v3/MessageWriterV3Test.java @@ -22,7 +22,7 @@ import java.util.stream.Stream; import org.neo4j.driver.Statement; -import org.neo4j.driver.internal.Bookmarks; +import org.neo4j.driver.internal.InternalBookmark; import org.neo4j.driver.internal.messaging.Message; import org.neo4j.driver.internal.messaging.MessageFormat; import org.neo4j.driver.internal.messaging.request.BeginMessage; @@ -66,14 +66,14 @@ protected Stream supportedMessages() // Bolt V3 messages new HelloMessage( "MyDriver/1.2.3", ((InternalAuthToken) basic( "neo4j", "neo4j" )).toMap() ), GOODBYE, - new BeginMessage( Bookmarks.from( "neo4j:bookmark:v1:tx123" ), ofSeconds( 5 ), singletonMap( "key", value( 42 ) ), READ, ABSENT_DB_NAME ), - new BeginMessage( Bookmarks.from( "neo4j:bookmark:v1:tx123" ), ofSeconds( 5 ), singletonMap( "key", value( 42 ) ), WRITE, ABSENT_DB_NAME ), + new BeginMessage( InternalBookmark.parse( "neo4j:bookmark:v1:tx123" ), ofSeconds( 5 ), singletonMap( "key", value( 42 ) ), READ, ABSENT_DB_NAME ), + new BeginMessage( InternalBookmark.parse( "neo4j:bookmark:v1:tx123" ), ofSeconds( 5 ), singletonMap( "key", value( 42 ) ), WRITE, ABSENT_DB_NAME ), COMMIT, ROLLBACK, autoCommitTxRunMessage( new Statement( "RETURN 1" ), ofSeconds( 5 ), singletonMap( "key", value( 42 ) ), ABSENT_DB_NAME, READ, - Bookmarks.from( "neo4j:bookmark:v1:tx1" ) ), + InternalBookmark.parse( "neo4j:bookmark:v1:tx1" ) ), autoCommitTxRunMessage( new Statement( "RETURN 1" ), ofSeconds( 5 ), singletonMap( "key", value( 42 ) ), ABSENT_DB_NAME, WRITE, - Bookmarks.from( "neo4j:bookmark:v1:tx1" ) ), + InternalBookmark.parse( "neo4j:bookmark:v1:tx1" ) ), explicitTxRunMessage( new Statement( "RETURN 1" ) ), PULL_ALL, DISCARD_ALL, @@ -81,9 +81,9 @@ protected Stream supportedMessages() // Bolt V3 messages with struct values autoCommitTxRunMessage( new Statement( "RETURN $x", singletonMap( "x", value( ZonedDateTime.now() ) ) ), ofSeconds( 1 ), emptyMap(), - ABSENT_DB_NAME, READ, Bookmarks.empty() ), + ABSENT_DB_NAME, READ, InternalBookmark.empty() ), autoCommitTxRunMessage( new Statement( "RETURN $x", singletonMap( "x", value( ZonedDateTime.now() ) ) ), ofSeconds( 1 ), emptyMap(), - ABSENT_DB_NAME, WRITE, Bookmarks.empty() ), + ABSENT_DB_NAME, WRITE, InternalBookmark.empty() ), explicitTxRunMessage( new Statement( "RETURN $x", singletonMap( "x", point( 42, 1, 2, 3 ) ) ) ) ); } 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 1b95c0f5ac..5078a77492 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 @@ -25,9 +25,9 @@ import org.neo4j.driver.AccessMode; import org.neo4j.driver.TransactionConfig; -import org.neo4j.driver.internal.Bookmarks; -import org.neo4j.driver.internal.BookmarksHolder; -import org.neo4j.driver.internal.DefaultBookmarksHolder; +import org.neo4j.driver.internal.BookmarkHolder; +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.StatementResultCursorFactory; @@ -73,45 +73,45 @@ protected Class expectedMessageFormatType() } @Override - protected void testFailedRunInAutoCommitTxWithWaitingForResponse( Bookmarks bookmarks, TransactionConfig config, AccessMode mode ) throws Exception + protected void testFailedRunInAutoCommitTxWithWaitingForResponse( InternalBookmark bookmark, TransactionConfig config, AccessMode mode ) throws Exception { // Given Connection connection = connectionMock( mode, protocol ); - BookmarksHolder bookmarksHolder = new DefaultBookmarksHolder( bookmarks ); + BookmarkHolder bookmarkHolder = new DefaultBookmarkHolder( bookmark ); CompletableFuture cursorFuture = - protocol.runInAutoCommitTransaction( connection, STATEMENT, bookmarksHolder, config, true ).asyncResult().toCompletableFuture(); + protocol.runInAutoCommitTransaction( connection, STATEMENT, bookmarkHolder, config, true ).asyncResult().toCompletableFuture(); - ResponseHandler runHandler = verifySessionRunInvoked( connection, bookmarks, config, mode, ABSENT_DB_NAME ); + ResponseHandler runHandler = verifySessionRunInvoked( connection, bookmark, config, mode, ABSENT_DB_NAME ); assertFalse( cursorFuture.isDone() ); // When I response to Run message with a failure runHandler.onFailure( new RuntimeException() ); // Then - assertEquals( bookmarks, bookmarksHolder.getBookmarks() ); + assertEquals( bookmark, bookmarkHolder.getBookmark() ); assertTrue( cursorFuture.isDone() ); assertNotNull( cursorFuture.get() ); } @Override - protected void testSuccessfulRunInAutoCommitTxWithWaitingForResponse( Bookmarks bookmarks, TransactionConfig config, AccessMode mode ) throws Exception + protected void testSuccessfulRunInAutoCommitTxWithWaitingForResponse( InternalBookmark bookmark, TransactionConfig config, AccessMode mode ) throws Exception { // Given Connection connection = connectionMock( mode, protocol ); - BookmarksHolder bookmarksHolder = new DefaultBookmarksHolder( bookmarks ); + BookmarkHolder bookmarkHolder = new DefaultBookmarkHolder( bookmark ); CompletableFuture cursorFuture = - protocol.runInAutoCommitTransaction( connection, STATEMENT, bookmarksHolder, config, true ).asyncResult().toCompletableFuture(); + protocol.runInAutoCommitTransaction( connection, STATEMENT, bookmarkHolder, config, true ).asyncResult().toCompletableFuture(); - ResponseHandler runHandler = verifySessionRunInvoked( connection, bookmarks, config, mode, ABSENT_DB_NAME ); + ResponseHandler runHandler = verifySessionRunInvoked( connection, bookmark, config, mode, ABSENT_DB_NAME ); assertFalse( cursorFuture.isDone() ); // When I response to the run message runHandler.onSuccess( emptyMap() ); // Then - assertEquals( bookmarks, bookmarksHolder.getBookmarks() ); + assertEquals( bookmark, bookmarkHolder.getBookmark() ); assertTrue( cursorFuture.isDone() ); assertNotNull( cursorFuture.get() ); } @@ -148,13 +148,13 @@ protected void testRunWithoutWaitingForRunResponse( boolean autoCommitTx, Transa { // Given Connection connection = connectionMock( mode, protocol ); - Bookmarks initialBookmarks = Bookmarks.from( "neo4j:bookmark:v1:tx987" ); + InternalBookmark initialBookmark = InternalBookmark.parse( "neo4j:bookmark:v1:tx987" ); CompletionStage cursorStage; if ( autoCommitTx ) { - BookmarksHolder bookmarksHolder = new DefaultBookmarksHolder( initialBookmarks ); - cursorStage = protocol.runInAutoCommitTransaction( connection, STATEMENT, bookmarksHolder, config, false ).asyncResult(); + BookmarkHolder bookmarkHolder = new DefaultBookmarkHolder( initialBookmark ); + cursorStage = protocol.runInAutoCommitTransaction( connection, STATEMENT, bookmarkHolder, config, false ).asyncResult(); } else { @@ -169,7 +169,7 @@ protected void testRunWithoutWaitingForRunResponse( boolean autoCommitTx, Transa // Then if ( autoCommitTx ) { - verifySessionRunInvoked( connection, initialBookmarks, config, mode, ABSENT_DB_NAME ); + verifySessionRunInvoked( connection, initialBookmark, config, mode, ABSENT_DB_NAME ); } else { @@ -184,15 +184,15 @@ protected void testDatabaseNameSupport( boolean autoCommitTx ) if ( autoCommitTx ) { StatementResultCursorFactory factory = - protocol.runInAutoCommitTransaction( connection, STATEMENT, BookmarksHolder.NO_OP, TransactionConfig.empty(), false ); + protocol.runInAutoCommitTransaction( connection, STATEMENT, BookmarkHolder.NO_OP, TransactionConfig.empty(), false ); await( factory.asyncResult() ); - verifySessionRunInvoked( connection, Bookmarks.empty(), TransactionConfig.empty(), AccessMode.WRITE, "foo" ); + verifySessionRunInvoked( connection, InternalBookmark.empty(), TransactionConfig.empty(), AccessMode.WRITE, "foo" ); } else { - CompletionStage txStage = protocol.beginTransaction( connection, Bookmarks.empty(), TransactionConfig.empty() ); + CompletionStage txStage = protocol.beginTransaction( connection, InternalBookmark.empty(), TransactionConfig.empty() ); await( txStage ); - verifyBeginInvoked( connection, Bookmarks.empty(), TransactionConfig.empty(), AccessMode.WRITE, "foo" ); + verifyBeginInvoked( connection, InternalBookmark.empty(), TransactionConfig.empty(), AccessMode.WRITE, "foo" ); } } @@ -201,9 +201,9 @@ private ResponseHandler verifyTxRunInvoked( Connection connection ) return verifyRunInvoked( connection, RunWithMetadataMessage.explicitTxRunMessage( STATEMENT ) ); } - private ResponseHandler verifySessionRunInvoked( Connection connection, Bookmarks bookmarks, TransactionConfig config, AccessMode mode, String databaseName ) + private ResponseHandler verifySessionRunInvoked( Connection connection, InternalBookmark bookmark, TransactionConfig config, AccessMode mode, String databaseName ) { - RunWithMetadataMessage runMessage = RunWithMetadataMessage.autoCommitTxRunMessage( STATEMENT, config, databaseName, mode, bookmarks ); + RunWithMetadataMessage runMessage = RunWithMetadataMessage.autoCommitTxRunMessage( STATEMENT, config, databaseName, mode, bookmark ); return verifyRunInvoked( connection, runMessage ); } @@ -220,12 +220,12 @@ private ResponseHandler verifyRunInvoked( Connection connection, RunWithMetadata return runHandlerCaptor.getValue(); } - private void verifyBeginInvoked( Connection connection, Bookmarks bookmarks, TransactionConfig config, AccessMode mode, String databaseName ) + private void verifyBeginInvoked( Connection connection, InternalBookmark bookmark, TransactionConfig config, AccessMode mode, String databaseName ) { ArgumentCaptor beginHandlerCaptor = ArgumentCaptor.forClass( ResponseHandler.class ); - BeginMessage beginMessage = new BeginMessage( bookmarks, config, databaseName, mode ); + BeginMessage beginMessage = new BeginMessage( bookmark, config, databaseName, mode ); - if( bookmarks.isEmpty() ) + if( bookmark.isEmpty() ) { verify( connection ).write( eq( beginMessage ), eq( NoOpResponseHandler.INSTANCE ) ); } diff --git a/driver/src/test/java/org/neo4j/driver/internal/messaging/v4/MessageWriterV4Test.java b/driver/src/test/java/org/neo4j/driver/internal/messaging/v4/MessageWriterV4Test.java index c9e4b78555..f39f77d63d 100644 --- a/driver/src/test/java/org/neo4j/driver/internal/messaging/v4/MessageWriterV4Test.java +++ b/driver/src/test/java/org/neo4j/driver/internal/messaging/v4/MessageWriterV4Test.java @@ -22,7 +22,7 @@ import java.util.stream.Stream; import org.neo4j.driver.Statement; -import org.neo4j.driver.internal.Bookmarks; +import org.neo4j.driver.internal.InternalBookmark; import org.neo4j.driver.internal.messaging.Message; import org.neo4j.driver.internal.messaging.MessageFormat; import org.neo4j.driver.internal.messaging.request.BeginMessage; @@ -73,23 +73,23 @@ protected Stream supportedMessages() // Bolt V3 messages new HelloMessage( "MyDriver/1.2.3", ((InternalAuthToken) basic( "neo4j", "neo4j" )).toMap() ), GOODBYE, - new BeginMessage( Bookmarks.from( "neo4j:bookmark:v1:tx123" ), ofSeconds( 5 ), singletonMap( "key", value( 42 ) ), READ, ABSENT_DB_NAME ), - new BeginMessage( Bookmarks.from( "neo4j:bookmark:v1:tx123" ), ofSeconds( 5 ), singletonMap( "key", value( 42 ) ), WRITE, "foo" ), + new BeginMessage( InternalBookmark.parse( "neo4j:bookmark:v1:tx123" ), ofSeconds( 5 ), singletonMap( "key", value( 42 ) ), READ, ABSENT_DB_NAME ), + new BeginMessage( InternalBookmark.parse( "neo4j:bookmark:v1:tx123" ), ofSeconds( 5 ), singletonMap( "key", value( 42 ) ), WRITE, "foo" ), COMMIT, ROLLBACK, RESET, autoCommitTxRunMessage( new Statement( "RETURN 1" ), ofSeconds( 5 ), singletonMap( "key", value( 42 ) ), ABSENT_DB_NAME, READ, - Bookmarks.from( "neo4j:bookmark:v1:tx1" ) ), + InternalBookmark.parse( "neo4j:bookmark:v1:tx1" ) ), autoCommitTxRunMessage( new Statement( "RETURN 1" ), ofSeconds( 5 ), singletonMap( "key", value( 42 ) ), "foo", WRITE, - Bookmarks.from( "neo4j:bookmark:v1:tx1" ) ), + InternalBookmark.parse( "neo4j:bookmark:v1:tx1" ) ), explicitTxRunMessage( new Statement( "RETURN 1" ) ), // Bolt V3 messages with struct values autoCommitTxRunMessage( new Statement( "RETURN $x", singletonMap( "x", value( ZonedDateTime.now() ) ) ), ofSeconds( 1 ), emptyMap(), - ABSENT_DB_NAME, READ, Bookmarks.empty() ), + ABSENT_DB_NAME, READ, InternalBookmark.empty() ), autoCommitTxRunMessage( new Statement( "RETURN $x", singletonMap( "x", value( ZonedDateTime.now() ) ) ), ofSeconds( 1 ), emptyMap(), "foo", - WRITE, Bookmarks.empty() ), + WRITE, InternalBookmark.empty() ), explicitTxRunMessage( new Statement( "RETURN $x", singletonMap( "x", point( 42, 1, 2, 3 ) ) ) ) ); } diff --git a/driver/src/test/java/org/neo4j/driver/internal/util/BookmarkUtils.java b/driver/src/test/java/org/neo4j/driver/internal/util/BookmarkUtils.java new file mode 100644 index 0000000000..5903450133 --- /dev/null +++ b/driver/src/test/java/org/neo4j/driver/internal/util/BookmarkUtils.java @@ -0,0 +1,87 @@ +/* + * 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.util; + +import org.hamcrest.Matcher; + +import java.util.HashSet; +import java.util.List; + +import org.neo4j.driver.internal.Bookmark; +import org.neo4j.driver.internal.InternalBookmark; + +import static org.hamcrest.Matchers.equalTo; +import static org.hamcrest.Matchers.notNullValue; +import static org.hamcrest.core.IsInstanceOf.instanceOf; +import static org.hamcrest.junit.MatcherAssert.assertThat; +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertNotNull; +import static org.junit.jupiter.api.Assertions.assertTrue; +import static org.neo4j.driver.internal.util.Iterables.asList; + +public class BookmarkUtils +{ + /** + * Bookmark is empty. + */ + public static void assertBookmarkIsEmpty( Bookmark bookmark ) + { + assertNotNull( bookmark ); + assertThat( bookmark, instanceOf( InternalBookmark.class ) ); + assertTrue( ((InternalBookmark) bookmark).isEmpty() ); + } + + /** + * Bookmark contains one single value + */ + public static void assertBookmarkContainsSingleValue( Bookmark bookmark ) + { + assertBookmarkContainsSingleValue( bookmark, notNullValue( String.class ) ); + } + + /** + * Bookmark contains one single value and the value matches the requirement set by matcher. + */ + public static void assertBookmarkContainsSingleValue( Bookmark bookmark, Matcher matcher ) + { + assertNotNull( bookmark ); + assertThat( bookmark, instanceOf( InternalBookmark.class ) ); + + List values = asList( ((InternalBookmark) bookmark).values() ); + assertThat( values.size(), equalTo( 1 ) ); + assertThat( values.get( 0 ), matcher ); + } + + /** + * Each bookmark contains one single value and the values are all different from each other. + */ + public static void assertBookmarksContainsSingleUniqueValues( Bookmark... bookmarks ) + { + int count = bookmarks.length; + HashSet bookmarkStrings = new HashSet<>(); + + for ( Bookmark bookmark : bookmarks ) + { + assertBookmarkContainsSingleValue( bookmark ); + List values = asList( ((InternalBookmark) bookmark).values() ); + bookmarkStrings.addAll( values ); + } + assertEquals( count, bookmarkStrings.size() ); + } +} diff --git a/driver/src/test/java/org/neo4j/driver/internal/util/MetadataExtractorTest.java b/driver/src/test/java/org/neo4j/driver/internal/util/MetadataExtractorTest.java index bf17bbd56a..fead13a76e 100644 --- a/driver/src/test/java/org/neo4j/driver/internal/util/MetadataExtractorTest.java +++ b/driver/src/test/java/org/neo4j/driver/internal/util/MetadataExtractorTest.java @@ -30,7 +30,8 @@ import org.neo4j.driver.exceptions.UntrustedServerException; import org.neo4j.driver.exceptions.value.Uncoercible; import org.neo4j.driver.internal.BoltServerAddress; -import org.neo4j.driver.internal.Bookmarks; +import org.neo4j.driver.internal.Bookmark; +import org.neo4j.driver.internal.InternalBookmark; import org.neo4j.driver.internal.spi.Connection; import org.neo4j.driver.internal.summary.InternalInputPosition; import org.neo4j.driver.summary.DatabaseInfo; @@ -356,33 +357,33 @@ void shouldExtractBookmark() { String bookmarkValue = "neo4j:bookmark:v1:tx123456"; - Bookmarks bookmarks = extractor.extractBookmarks( singletonMap( "bookmark", value( bookmarkValue ) ) ); + Bookmark bookmark = extractor.extractBookmarks( singletonMap( "bookmark", value( bookmarkValue ) ) ); - assertEquals( Bookmarks.from( bookmarkValue ), bookmarks ); + assertEquals( InternalBookmark.parse( bookmarkValue ), bookmark ); } @Test void shouldExtractNoBookmarkWhenMetadataContainsNull() { - Bookmarks bookmarks = extractor.extractBookmarks( singletonMap( "bookmark", null ) ); + Bookmark bookmark = extractor.extractBookmarks( singletonMap( "bookmark", null ) ); - assertEquals( Bookmarks.empty(), bookmarks ); + assertEquals( InternalBookmark.empty(), bookmark ); } @Test void shouldExtractNoBookmarkWhenMetadataContainsNullValue() { - Bookmarks bookmarks = extractor.extractBookmarks( singletonMap( "bookmark", Values.NULL ) ); + Bookmark bookmark = extractor.extractBookmarks( singletonMap( "bookmark", Values.NULL ) ); - assertEquals( Bookmarks.empty(), bookmarks ); + assertEquals( InternalBookmark.empty(), bookmark ); } @Test void shouldExtractNoBookmarkWhenMetadataContainsValueOfIncorrectType() { - Bookmarks bookmarks = extractor.extractBookmarks( singletonMap( "bookmark", value( 42 ) ) ); + Bookmark bookmark = extractor.extractBookmarks( singletonMap( "bookmark", value( 42 ) ) ); - assertEquals( Bookmarks.empty(), bookmarks ); + assertEquals( InternalBookmark.empty(), bookmark ); } @Test diff --git a/driver/src/test/java/org/neo4j/driver/stress/AbstractContext.java b/driver/src/test/java/org/neo4j/driver/stress/AbstractContext.java index 40568d8f04..ec29589e55 100644 --- a/driver/src/test/java/org/neo4j/driver/stress/AbstractContext.java +++ b/driver/src/test/java/org/neo4j/driver/stress/AbstractContext.java @@ -21,12 +21,13 @@ import java.util.concurrent.atomic.AtomicInteger; import java.util.concurrent.atomic.AtomicLong; +import org.neo4j.driver.internal.Bookmark; import org.neo4j.driver.summary.ResultSummary; public abstract class AbstractContext { private volatile boolean stopped; - private volatile String bookmark; + private volatile Bookmark bookmark; private final AtomicLong readNodesCount = new AtomicLong(); private final AtomicLong createdNodesCount = new AtomicLong(); private final AtomicInteger bookmarkFailures = new AtomicInteger(); @@ -41,12 +42,12 @@ public final void stop() this.stopped = true; } - public final String getBookmark() + public final Bookmark getBookmark() { return bookmark; } - public final void setBookmark( String bookmark ) + public final void setBookmark( Bookmark bookmark ) { this.bookmark = bookmark; } 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 f76ec56a07..ce56d8d6c1 100644 --- a/driver/src/test/java/org/neo4j/driver/stress/AbstractStressTestBase.java +++ b/driver/src/test/java/org/neo4j/driver/stress/AbstractStressTestBase.java @@ -63,6 +63,7 @@ import org.neo4j.driver.async.AsyncSession; import org.neo4j.driver.async.AsyncTransaction; import org.neo4j.driver.async.StatementResultCursor; +import org.neo4j.driver.internal.Bookmark; import org.neo4j.driver.internal.InternalDriver; import org.neo4j.driver.internal.logging.DevNullLogger; import org.neo4j.driver.internal.util.EnabledOnNeo4jWith; @@ -155,14 +156,14 @@ void rxApiStressTest() throws Throwable @Test void blockingApiBigDataTest() { - String bookmark = createNodesBlocking( bigDataTestBatchCount(), BIG_DATA_TEST_BATCH_SIZE, driver ); + Bookmark bookmark = createNodesBlocking( bigDataTestBatchCount(), BIG_DATA_TEST_BATCH_SIZE, driver ); readNodesBlocking( driver, bookmark, BIG_DATA_TEST_NODE_COUNT ); } @Test void asyncApiBigDataTest() throws Throwable { - String bookmark = createNodesAsync( bigDataTestBatchCount(), BIG_DATA_TEST_BATCH_SIZE, driver ); + Bookmark bookmark = createNodesAsync( bigDataTestBatchCount(), BIG_DATA_TEST_BATCH_SIZE, driver ); readNodesAsync( driver, bookmark, BIG_DATA_TEST_NODE_COUNT ); } @@ -170,7 +171,7 @@ void asyncApiBigDataTest() throws Throwable @EnabledOnNeo4jWith( Neo4jFeature.BOLT_V4 ) void rxApiBigDataTest() throws Throwable { - String bookmark = createNodesRx( bigDataTestBatchCount(), BIG_DATA_TEST_BATCH_SIZE, driver ); + Bookmark bookmark = createNodesRx( bigDataTestBatchCount(), BIG_DATA_TEST_BATCH_SIZE, driver ); readNodesRx( driver, bookmark, BIG_DATA_TEST_NODE_COUNT ); } @@ -502,9 +503,9 @@ private static int bigDataTestBatchCount() return BIG_DATA_TEST_NODE_COUNT / BIG_DATA_TEST_BATCH_SIZE; } - private static String createNodesBlocking( int batchCount, int batchSize, Driver driver ) + private static Bookmark createNodesBlocking( int batchCount, int batchSize, Driver driver ) { - String bookmark; + Bookmark bookmark; long start = System.nanoTime(); try ( Session session = driver.session() ) @@ -522,7 +523,7 @@ private static String createNodesBlocking( int batchCount, int batchSize, Driver return bookmark; } - private static void readNodesBlocking( Driver driver, String bookmark, int expectedNodeCount ) + private static void readNodesBlocking( Driver driver, Bookmark bookmark, int expectedNodeCount ) { long start = System.nanoTime(); try ( Session session = driver.session( builder().withBookmarks( bookmark ).build() ) ) @@ -553,7 +554,7 @@ private static void readNodesBlocking( Driver driver, String bookmark, int expec System.out.println( "Reading nodes with blocking API took: " + NANOSECONDS.toMillis( end - start ) + "ms" ); } - private static String createNodesAsync( int batchCount, int batchSize, Driver driver ) throws Throwable + private static Bookmark createNodesAsync( int batchCount, int batchSize, Driver driver ) throws Throwable { long start = System.nanoTime(); @@ -581,7 +582,7 @@ private static String createNodesAsync( int batchCount, int batchSize, Driver dr return session.lastBookmark(); } - private static void readNodesAsync( Driver driver, String bookmark, int expectedNodeCount ) throws Throwable + private static void readNodesAsync( Driver driver, Bookmark bookmark, int expectedNodeCount ) throws Throwable { long start = System.nanoTime(); @@ -618,7 +619,7 @@ private static void readNodesAsync( Driver driver, String bookmark, int expected System.out.println( "Reading nodes with async API took: " + NANOSECONDS.toMillis( end - start ) + "ms" ); } - private String createNodesRx( int batchCount, int batchSize, InternalDriver driver ) + private Bookmark createNodesRx( int batchCount, int batchSize, InternalDriver driver ) { long start = System.nanoTime(); @@ -642,7 +643,7 @@ private Publisher createNodesInTxRx( RxTransaction tx, int batchIndex, int } ) ); } - private void readNodesRx( InternalDriver driver, String bookmark, int expectedNodeCount ) + private void readNodesRx( InternalDriver driver, Bookmark bookmark, int expectedNodeCount ) { long start = System.nanoTime(); 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 392529ce82..00c5dcfd7b 100644 --- a/driver/src/test/java/org/neo4j/driver/stress/CausalClusteringIT.java +++ b/driver/src/test/java/org/neo4j/driver/stress/CausalClusteringIT.java @@ -59,6 +59,7 @@ import org.neo4j.driver.exceptions.SessionExpiredException; import org.neo4j.driver.integration.NestedQueries; import org.neo4j.driver.internal.BoltServerAddress; +import org.neo4j.driver.internal.Bookmark; import org.neo4j.driver.internal.cluster.RoutingSettings; import org.neo4j.driver.internal.retry.RetrySettings; import org.neo4j.driver.internal.util.DisabledOnNeo4jWith; @@ -90,6 +91,7 @@ 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.internal.InternalBookmark.parse; import static org.neo4j.driver.internal.logging.DevNullLogging.DEV_NULL_LOGGING; import static org.neo4j.driver.internal.util.Matchers.connectionAcquisitionTimeoutError; import static org.neo4j.driver.internal.util.Neo4jFeature.BOLT_V3; @@ -185,7 +187,7 @@ void bookmarksShouldWorkWithDriverPinnedToSingleServer() throws Exception try ( Driver driver = createDriver( leader.getBoltUri() ) ) { - String bookmark = inExpirableSession( driver, Driver::session, session -> + Bookmark bookmark = inExpirableSession( driver, Driver::session, session -> { try ( Transaction tx = session.beginTransaction() ) { @@ -222,7 +224,7 @@ void shouldUseBookmarkFromAReadSessionInAWriteSession() throws Exception return null; } ); - final String bookmark; + final Bookmark bookmark; try ( Session session = driver.session( builder().withDefaultAccessMode( AccessMode.READ ).build() ) ) { try ( Transaction tx = session.beginTransaction() ) @@ -310,14 +312,15 @@ void shouldDropBrokenOldConnections() throws Exception @Test void beginTransactionThrowsForInvalidBookmark() { - String invalidBookmark = "hi, this is an invalid bookmark"; + final String text = "hi, this is an invalid bookmark"; + Bookmark invalidBookmark = parse( text ); ClusterMember leader = clusterRule.getCluster().leader(); try ( Driver driver = createDriver( leader.getBoltUri() ); Session session = driver.session( builder().withBookmarks( invalidBookmark ).build() ) ) { ClientException e = assertThrows( ClientException.class, session::beginTransaction ); - assertThat( e.getMessage(), containsString( invalidBookmark ) ); + assertThat( e.getMessage(), containsString( text ) ); } } @@ -342,7 +345,7 @@ void shouldHandleGracefulLeaderSwitch() throws Exception assertThrows( (Class) SessionExpiredException.class, ((AutoCloseable) tx1)::close ); session1.close(); - String bookmark = inExpirableSession( driver, Driver::session, session -> + Bookmark bookmark = inExpirableSession( driver, Driver::session, session -> { try ( Transaction tx = session.beginTransaction() ) { @@ -400,7 +403,7 @@ void shouldServeReadsWhenMajorityOfCoresAreDead() try ( Driver driver = createDriver( leader.getRoutingUri() ) ) { - String bookmark; + Bookmark bookmark; try ( Session session = driver.session() ) { int writeResult = session.writeTransaction( tx -> @@ -459,14 +462,14 @@ void shouldAcceptMultipleBookmarks() throws Exception try ( Driver driver = createDriver( leader.getRoutingUri() ) ) { - List> futures = new ArrayList<>(); + List> futures = new ArrayList<>(); for ( int i = 0; i < threadCount; i++ ) { futures.add( executor.submit( createNodeAndGetBookmark( driver, label, property, value ) ) ); } - List bookmarks = new ArrayList<>(); - for ( Future future : futures ) + List bookmarks = new ArrayList<>(); + for ( Future future : futures ) { bookmarks.add( future.get( 10, SECONDS ) ); } @@ -588,7 +591,7 @@ RoutingSettings.DEFAULT, RetrySettings.DEFAULT, configWithoutLogging() ) ) } // rediscovery should happen for the new write query - String session4Bookmark = createNodeAndGetBookmark( driver.session(), "Node3", "name", "Node3" ); + Bookmark session4Bookmark = createNodeAndGetBookmark( driver.session(), "Node3", "name", "Node3" ); try ( Session session5 = driver.session( builder().withBookmarks( session4Bookmark ).build() ) ) { assertEquals( 1, countNodes( session5, "Node3", "name", "Node3" ) ); @@ -749,7 +752,7 @@ private int executeWriteAndReadThroughBoltOnFirstAvailableAddress( ClusterMember } } - private Function createWritableSession( final String bookmark ) + private Function createWritableSession( final Bookmark bookmark ) { return driver -> driver.session( builder().withDefaultAccessMode( AccessMode.WRITE ).withBookmarks( bookmark ).build() ); } @@ -787,7 +790,7 @@ private T inExpirableSession( Driver driver, Function acquir throw new TimeoutException( "Transaction did not succeed in time" ); } - private void ensureNodeVisible( Cluster cluster, String name, String bookmark ) + private void ensureNodeVisible( Cluster cluster, String name, Bookmark bookmark ) { for ( ClusterMember member : cluster.members() ) { @@ -796,7 +799,7 @@ private void ensureNodeVisible( Cluster cluster, String name, String bookmark ) } } - private int countNodesUsingDirectDriver( ClusterMember member, final String name, String bookmark ) + private int countNodesUsingDirectDriver( ClusterMember member, final String name, Bookmark bookmark ) { Driver driver = clusterRule.getCluster().getDirectDriver( member ); try ( Session session = driver.session( builder().withBookmarks( bookmark ).build() ) ) @@ -990,13 +993,13 @@ private static int countNodes( Session session, String label, String property, S return session.readTransaction( tx -> runCountNodes( tx, label, property, value ) ); } - private static Callable createNodeAndGetBookmark( Driver driver, String label, String property, + private static Callable createNodeAndGetBookmark( Driver driver, String label, String property, String value ) { return () -> createNodeAndGetBookmark( driver.session(), label, property, value ); } - private static String createNodeAndGetBookmark( Session session, String label, String property, String value ) + private static Bookmark createNodeAndGetBookmark( Session session, String label, String property, String value ) { try ( Session localSession = session ) { diff --git a/driver/src/test/java/org/neo4j/driver/util/Neo4jSettings.java b/driver/src/test/java/org/neo4j/driver/util/Neo4jSettings.java index f93f51bed0..642fd28c17 100644 --- a/driver/src/test/java/org/neo4j/driver/util/Neo4jSettings.java +++ b/driver/src/test/java/org/neo4j/driver/util/Neo4jSettings.java @@ -28,11 +28,9 @@ public class Neo4jSettings { - public static final String AUTH_ENABLED = "dbms.security.auth_enabled"; public static final String DATA_DIR = "dbms.directories.data"; - public static final String CERT_DIR = "dbms.directories.certificates"; public static final String IMPORT_DIR = "dbms.directories.import"; - public static final String LISTEN_ADDR = "dbms.connectors.default_listen_address"; // only valid for 3.1+ + public static final String LISTEN_ADDR = "dbms.connectors.default_listen_address"; public static final String IPV6_ENABLED_ADDR = "::"; public static final String PAGE_CACHE_SIZE = "dbms.memory.pagecache.size"; public static final String BOLT_TLS_LEVEL = "dbms.connector.bolt.tls_level"; @@ -63,17 +61,13 @@ public class Neo4jSettings public static final Neo4jSettings TEST_SETTINGS = new Neo4jSettings( map( "dbms.backup.enabled", "false", - "dbms.memory.pagecache.size", "100m", "dbms.connector.http.listen_address", ":" + CURRENT_HTTP_PORT, "dbms.connector.https.listen_address", ":" + CURRENT_HTTPS_PORT, "dbms.connector.bolt.listen_address", ":" + CURRENT_BOLT_PORT, "dbms.windows_service_name", WINDOWS_SERVICE_NAME, - CERT_DIR, DEFAULT_CERT_DIR, DATA_DIR, DEFAULT_DATA_DIR, IMPORT_DIR, DEFAULT_IMPORT_DIR, - AUTH_ENABLED, "true", - PAGE_CACHE_SIZE, DEFAULT_PAGE_CACHE_SIZE, BOLT_TLS_LEVEL, DEFAULT_BOLT_TLS_LEVEL, LISTEN_ADDR, IPV6_ENABLED_ADDR ), Collections.emptySet() ); diff --git a/driver/src/test/java/org/neo4j/driver/util/SessionExtension.java b/driver/src/test/java/org/neo4j/driver/util/SessionExtension.java index ce4891e7e4..ccedece3f5 100644 --- a/driver/src/test/java/org/neo4j/driver/util/SessionExtension.java +++ b/driver/src/test/java/org/neo4j/driver/util/SessionExtension.java @@ -32,7 +32,7 @@ import org.neo4j.driver.TransactionConfig; import org.neo4j.driver.TransactionWork; import org.neo4j.driver.Value; -import org.neo4j.driver.types.TypeSystem; +import org.neo4j.driver.internal.Bookmark; /** * A little utility for integration testing, this provides tests with a session they can work with. @@ -107,7 +107,7 @@ public T writeTransaction( TransactionWork work, TransactionConfig config } @Override - public String lastBookmark() + public Bookmark lastBookmark() { return realSession.lastBookmark(); } diff --git a/driver/src/test/java/org/neo4j/driver/util/TemporalUtil.java b/driver/src/test/java/org/neo4j/driver/util/TemporalUtil.java index be2eafbb0b..23b82ca18e 100644 --- a/driver/src/test/java/org/neo4j/driver/util/TemporalUtil.java +++ b/driver/src/test/java/org/neo4j/driver/util/TemporalUtil.java @@ -55,11 +55,11 @@ public final class TemporalUtil * Other time zones are not working correctly in some Java versions */ private static final List BLACKLISTED_ZONE_IDS = Arrays.asList( - // removed "Canada/East-Saskatchewan", - // is broken for years in distant future in Java 11 (example of a misbehaving date: +71235332-08-09T22:41:50.738292551-06:00[Chile/EasterIsland]) "Chile/EasterIsland", - "Africa/Casablanca" + "Africa/Casablanca", + "tzid", + "Asia/Qostanay" ); private TemporalUtil() 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 1d063b2451..277fb1ab37 100644 --- a/driver/src/test/java/org/neo4j/driver/util/TestUtil.java +++ b/driver/src/test/java/org/neo4j/driver/util/TestUtil.java @@ -49,8 +49,9 @@ import org.neo4j.driver.StatementResult; import org.neo4j.driver.exceptions.ServiceUnavailableException; import org.neo4j.driver.internal.BoltServerAddress; -import org.neo4j.driver.internal.Bookmarks; -import org.neo4j.driver.internal.DefaultBookmarksHolder; +import org.neo4j.driver.internal.Bookmark; +import org.neo4j.driver.internal.DefaultBookmarkHolder; +import org.neo4j.driver.internal.InternalBookmark; import org.neo4j.driver.internal.async.NetworkSession; import org.neo4j.driver.internal.async.connection.EventLoopGroupFactory; import org.neo4j.driver.internal.handlers.BeginTxResponseHandler; @@ -90,8 +91,8 @@ import static org.mockito.Mockito.verify; import static org.mockito.Mockito.when; import static org.neo4j.driver.AccessMode.WRITE; -import static org.neo4j.driver.internal.Bookmarks.empty; import static org.neo4j.driver.SessionConfig.builder; +import static org.neo4j.driver.internal.InternalBookmark.empty; import static org.neo4j.driver.internal.logging.DevNullLogging.DEV_NULL_LOGGING; import static org.neo4j.driver.internal.messaging.request.MultiDatabaseUtil.ABSENT_DB_NAME; import static org.neo4j.driver.internal.util.Futures.completedWithNull; @@ -219,7 +220,7 @@ public static Set asOrderedSet( T... elements ) return new LinkedHashSet<>( Arrays.asList( elements ) ); } - public static long countNodes( Driver driver, String bookmark ) + public static long countNodes( Driver driver, Bookmark bookmark ) { try ( Session session = driver.session( builder().withBookmarks( bookmark ).build() ) ) { @@ -227,7 +228,7 @@ public static long countNodes( Driver driver, String bookmark ) } } - public static String cleanDb( Driver driver ) + public static Bookmark cleanDb( Driver driver ) { try ( Session session = driver.session() ) { @@ -236,12 +237,12 @@ public static String cleanDb( Driver driver ) } } - public static NetworkSession newSession( ConnectionProvider connectionProvider, Bookmarks x ) + public static NetworkSession newSession( ConnectionProvider connectionProvider, InternalBookmark x ) { return newSession( connectionProvider, WRITE, x ); } - private static NetworkSession newSession( ConnectionProvider connectionProvider, AccessMode mode, Bookmarks x ) + private static NetworkSession newSession( ConnectionProvider connectionProvider, AccessMode mode, InternalBookmark x ) { return newSession( connectionProvider, mode, new FixedRetryLogic( 0 ), x ); } @@ -262,9 +263,9 @@ public static NetworkSession newSession( ConnectionProvider connectionProvider ) } public static NetworkSession newSession( ConnectionProvider connectionProvider, AccessMode mode, - RetryLogic retryLogic, Bookmarks bookmarks ) + RetryLogic retryLogic, InternalBookmark bookmark ) { - return new NetworkSession( connectionProvider, retryLogic, ABSENT_DB_NAME, mode, new DefaultBookmarksHolder( bookmarks ), DEV_NULL_LOGGING ); + return new NetworkSession( connectionProvider, retryLogic, ABSENT_DB_NAME, mode, new DefaultBookmarkHolder( bookmark ), DEV_NULL_LOGGING ); } public static void verifyRun( Connection connection, String query ) @@ -299,12 +300,12 @@ public static void verifyRollbackTx( Connection connection ) public static void verifyBeginTx( Connection connectionMock ) { - verifyBeginTx( connectionMock, Bookmarks.empty() ); + verifyBeginTx( connectionMock, empty() ); } - public static void verifyBeginTx( Connection connectionMock, Bookmarks bookmarks ) + public static void verifyBeginTx( Connection connectionMock, InternalBookmark bookmark ) { - if ( bookmarks.isEmpty() ) + if ( bookmark.isEmpty() ) { verify( connectionMock ).write( any( BeginMessage.class ), eq( NoOpResponseHandler.INSTANCE ) ); } diff --git a/driver/src/test/java/org/neo4j/driver/util/cc/Cluster.java b/driver/src/test/java/org/neo4j/driver/util/cc/Cluster.java index cf16d83ed3..0b214e1250 100644 --- a/driver/src/test/java/org/neo4j/driver/util/cc/Cluster.java +++ b/driver/src/test/java/org/neo4j/driver/util/cc/Cluster.java @@ -31,6 +31,7 @@ import org.neo4j.driver.Driver; import org.neo4j.driver.Record; import org.neo4j.driver.internal.BoltServerAddress; +import org.neo4j.driver.internal.Bookmark; import org.neo4j.driver.util.TestUtil; import org.neo4j.driver.util.cc.ClusterMemberRoleDiscoveryFactory.ClusterMemberRoleDiscovery; @@ -77,7 +78,7 @@ public void deleteData() { // execute write query to remove all nodes and retrieve bookmark Driver driverToLeader = clusterDrivers.getDriver( leader() ); - String bookmark = TestUtil.cleanDb( driverToLeader ); + Bookmark bookmark = TestUtil.cleanDb( driverToLeader ); if ( bookmark == null ) { throw new IllegalStateException( "Cleanup of the database did not produce a bookmark" ); diff --git a/driver/src/test/resources/acquire_endpoints_v4.script b/driver/src/test/resources/acquire_endpoints_v4.script index cece16f684..5846ac3ea0 100644 --- a/driver/src/test/resources/acquire_endpoints_v4.script +++ b/driver/src/test/resources/acquire_endpoints_v4.script @@ -3,7 +3,7 @@ !: AUTO HELLO !: AUTO GOODBYE -C: RUN "CALL dbms.routing.getRoutingTable($context, $database)" {"context": {}, "database": "myDatabase"} {"mode": "r", "db": "system"} +C: RUN "CALL dbms.routing.getRoutingTable($context, $database)" {"context": {}, "database": "mydatabase"} {"mode": "r", "db": "system"} PULL {"n": -1} S: SUCCESS {"fields": ["ttl", "servers"]} RECORD [9223372036854775807, [{"addresses": ["127.0.0.1:9007","127.0.0.1:9008"],"role": "WRITE"}, {"addresses": ["127.0.0.1:9005","127.0.0.1:9006"], "role": "READ"},{"addresses": ["127.0.0.1:9001","127.0.0.1:9002","127.0.0.1:9003"], "role": "ROUTE"}]] diff --git a/driver/src/test/resources/acquire_endpoints_v4_database_not_found.script b/driver/src/test/resources/acquire_endpoints_v4_database_not_found.script index 29e8e0ba5e..f1efb06b34 100644 --- a/driver/src/test/resources/acquire_endpoints_v4_database_not_found.script +++ b/driver/src/test/resources/acquire_endpoints_v4_database_not_found.script @@ -3,7 +3,7 @@ !: AUTO HELLO !: AUTO GOODBYE -C: RUN "CALL dbms.routing.getRoutingTable($context, $database)" {"context": {}, "database": "myDatabase"} {"mode": "r", "db": "system"} +C: RUN "CALL dbms.routing.getRoutingTable($context, $database)" {"context": {}, "database": "mydatabase"} {"mode": "r", "db": "system"} PULL {"n": -1} S: FAILURE {"code": "Neo.ClientError.Database.DatabaseNotFound", "message": "wut!"} IGNORED diff --git a/driver/src/test/resources/acquire_endpoints_v4_empty.script b/driver/src/test/resources/acquire_endpoints_v4_empty.script index 3bafae3745..32c5862d4e 100644 --- a/driver/src/test/resources/acquire_endpoints_v4_empty.script +++ b/driver/src/test/resources/acquire_endpoints_v4_empty.script @@ -3,7 +3,7 @@ !: AUTO HELLO !: AUTO GOODBYE -C: RUN "CALL dbms.routing.getRoutingTable($context, $database)" {"context": {}, "database": "myDatabase"} {"mode": "r", "db": "system"} +C: RUN "CALL dbms.routing.getRoutingTable($context, $database)" {"context": {}, "database": "mydatabase"} {"mode": "r", "db": "system"} PULL {"n": -1} S: SUCCESS {"fields": ["ttl", "servers"]} RECORD [9223372036854775807, []] diff --git a/driver/src/test/resources/acquire_endpoints_v4_multi_db.script b/driver/src/test/resources/acquire_endpoints_v4_multi_db.script index 5b29ec5d7b..f454ec7c7f 100644 --- a/driver/src/test/resources/acquire_endpoints_v4_multi_db.script +++ b/driver/src/test/resources/acquire_endpoints_v4_multi_db.script @@ -3,12 +3,12 @@ !: AUTO HELLO !: AUTO GOODBYE -C: RUN "CALL dbms.routing.getRoutingTable($context, $database)" {"context": {}, "database": "Unreachable"} {"mode": "r", "db": "system"} +C: RUN "CALL dbms.routing.getRoutingTable($context, $database)" {"context": {}, "database": "unreachable"} {"mode": "r", "db": "system"} PULL {"n": -1} S: SUCCESS {"fields": ["ttl", "servers"]} RECORD [9223372036854775807, []] SUCCESS {} -C: RUN "CALL dbms.routing.getRoutingTable($context, $database)" {"context": {}, "database": "myDatabase"} {"mode": "r", "db": "system"} +C: RUN "CALL dbms.routing.getRoutingTable($context, $database)" {"context": {}, "database": "mydatabase"} {"mode": "r", "db": "system"} PULL {"n": -1} S: SUCCESS {"fields": ["ttl", "servers"]} RECORD [9223372036854775807, [{"addresses": ["127.0.0.1:9007","127.0.0.1:9008"],"role": "WRITE"}, {"addresses": ["127.0.0.1:9005","127.0.0.1:9006"], "role": "READ"},{"addresses": ["127.0.0.1:9001","127.0.0.1:9002","127.0.0.1:9003"], "role": "ROUTE"}]] diff --git a/driver/src/test/resources/acquire_endpoints_v4_verify_connectivity.script b/driver/src/test/resources/acquire_endpoints_v4_verify_connectivity.script index f9fa055f14..1511bfc923 100644 --- a/driver/src/test/resources/acquire_endpoints_v4_verify_connectivity.script +++ b/driver/src/test/resources/acquire_endpoints_v4_verify_connectivity.script @@ -8,7 +8,7 @@ C: RUN "CALL dbms.routing.getRoutingTable($context, $database)" {"context": {}, S: SUCCESS {"fields": ["ttl", "servers"]} RECORD [9223372036854775807, [{"addresses": ["127.0.0.1:9007","127.0.0.1:9008"],"role": "WRITE"}, {"addresses": ["127.0.0.1:9005","127.0.0.1:9006"], "role": "READ"},{"addresses": ["127.0.0.1:9001","127.0.0.1:9002","127.0.0.1:9003"], "role": "ROUTE"}]] SUCCESS {} -C: RUN "CALL dbms.routing.getRoutingTable($context, $database)" {"context": {}, "database": "myDatabase"} {"mode": "r", "db": "system"} +C: RUN "CALL dbms.routing.getRoutingTable($context, $database)" {"context": {}, "database": "mydatabase"} {"mode": "r", "db": "system"} PULL {"n": -1} S: SUCCESS {"fields": ["ttl", "servers"]} RECORD [9223372036854775807, [{"addresses": ["127.0.0.1:9007","127.0.0.1:9008"],"role": "WRITE"}, {"addresses": ["127.0.0.1:9005","127.0.0.1:9006"], "role": "READ"},{"addresses": ["127.0.0.1:9001","127.0.0.1:9002","127.0.0.1:9003"], "role": "ROUTE"}]] diff --git a/driver/src/test/resources/acquire_endpoints_v4_with_bookmark.script b/driver/src/test/resources/acquire_endpoints_v4_with_bookmark.script new file mode 100644 index 0000000000..7bc62a01ec --- /dev/null +++ b/driver/src/test/resources/acquire_endpoints_v4_with_bookmark.script @@ -0,0 +1,10 @@ +!: BOLT 4 +!: AUTO RESET +!: AUTO HELLO +!: AUTO GOODBYE + +C: RUN "CALL dbms.routing.getRoutingTable($context, $database)" {"context": {}, "database": "foo"} {"mode": "r", "db": "system", "bookmarks": ["sys:1234", "foo:5678"]} + PULL {"n": -1} +S: SUCCESS {"fields": ["ttl", "servers"]} + RECORD [9223372036854775807, [{"addresses": ["127.0.0.1:9007","127.0.0.1:9008"],"role": "WRITE"}, {"addresses": ["127.0.0.1:9005","127.0.0.1:9006"], "role": "READ"},{"addresses": ["127.0.0.1:9001","127.0.0.1:9002","127.0.0.1:9003"], "role": "ROUTE"}]] + SUCCESS {"bookmark": "sys:2234"} diff --git a/driver/src/test/resources/hello_run_exit.script b/driver/src/test/resources/hello_run_exit.script index 498f2c7612..cb4fe6f95e 100644 --- a/driver/src/test/resources/hello_run_exit.script +++ b/driver/src/test/resources/hello_run_exit.script @@ -1,5 +1,6 @@ !: BOLT 3 !: AUTO RESET +!: AUTO GOODBYE C: HELLO {"scheme": "none", "user_agent": "neo4j-java/dev"} S: SUCCESS {"server": "Neo4j/3.5.0", "connection_id": "bolt-123456789"} diff --git a/driver/src/test/resources/read_server_v3_read_with_bookmark.script b/driver/src/test/resources/read_server_v3_read_with_bookmark.script new file mode 100644 index 0000000000..c44e456e02 --- /dev/null +++ b/driver/src/test/resources/read_server_v3_read_with_bookmark.script @@ -0,0 +1,12 @@ +!: BOLT 3 +!: AUTO RESET +!: AUTO HELLO +!: AUTO GOODBYE + +C: RUN "MATCH (n) RETURN n.name" {} { "mode": "r", "bookmarks": ["sys:1234", "foo:5678"] } + PULL_ALL +S: SUCCESS {"fields": ["n.name"]} + RECORD ["Bob"] + RECORD ["Alice"] + RECORD ["Tina"] + SUCCESS { "bookmark": "foo:6678" } diff --git a/driver/src/test/resources/read_server_v4_read.script b/driver/src/test/resources/read_server_v4_read.script index a554ba56c2..df53ef4d5f 100644 --- a/driver/src/test/resources/read_server_v4_read.script +++ b/driver/src/test/resources/read_server_v4_read.script @@ -3,7 +3,7 @@ !: AUTO HELLO !: AUTO GOODBYE -C: RUN "MATCH (n) RETURN n.name" {} { "mode": "r", "db": "myDatabase" } +C: RUN "MATCH (n) RETURN n.name" {} { "mode": "r", "db": "mydatabase" } PULL { "n": -1 } S: SUCCESS {"fields": ["n.name"]} RECORD ["Bob"] 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 d910f194cd..10224bd1f9 100644 --- a/driver/src/test/resources/read_server_v4_read_tx.script +++ b/driver/src/test/resources/read_server_v4_read_tx.script @@ -3,7 +3,7 @@ !: AUTO HELLO !: AUTO GOODBYE -C: BEGIN { "mode": "r", "db": "myDatabase" } +C: BEGIN { "mode": "r", "db": "mydatabase" } S: SUCCESS {} C: RUN "MATCH (n) RETURN n.name" {} {} PULL { "n": -1 } 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 new file mode 100644 index 0000000000..1e793b90d0 --- /dev/null +++ b/driver/src/test/resources/read_server_v4_read_with_bookmark.script @@ -0,0 +1,12 @@ +!: BOLT 4 +!: AUTO RESET +!: AUTO HELLO +!: AUTO GOODBYE + +C: RUN "MATCH (n) RETURN n.name" {} { "mode": "r", "db": "foo", "bookmarks": ["sys:1234", "foo:5678"] } + PULL { "n": -1 } +S: SUCCESS {"fields": ["n.name"]} + RECORD ["Bob"] + RECORD ["Alice"] + RECORD ["Tina"] + SUCCESS { "bookmark": "foo:6678" } diff --git a/examples/src/main/java/org/neo4j/docs/driver/PassBookmarkExample.java b/examples/src/main/java/org/neo4j/docs/driver/PassBookmarkExample.java index 88820ded8a..cf1cac4beb 100644 --- a/examples/src/main/java/org/neo4j/docs/driver/PassBookmarkExample.java +++ b/examples/src/main/java/org/neo4j/docs/driver/PassBookmarkExample.java @@ -27,6 +27,7 @@ import org.neo4j.driver.Session; import org.neo4j.driver.StatementResult; import org.neo4j.driver.Transaction; +import org.neo4j.driver.internal.Bookmark; import static org.neo4j.driver.Values.parameters; import static org.neo4j.driver.SessionConfig.builder; @@ -87,7 +88,7 @@ private StatementResult printFriends( final Transaction tx ) public void addEmployAndMakeFriends() { // To collect the session bookmarks - List savedBookmarks = new ArrayList<>(); + List savedBookmarks = new ArrayList<>(); // Create the first person and employment relationship. try ( Session session1 = driver.session( builder().withDefaultAccessMode( AccessMode.WRITE ).build() ) )