From 6516dbc23982533bea663801ef9afbbc9c00e06e Mon Sep 17 00:00:00 2001 From: Dmitriy Tverdiakov Date: Tue, 31 Dec 2024 17:03:55 +0000 Subject: [PATCH] Introduce home database resolution cache This update adds support for Bolt protocol 5.8 and introduces home database resolution cache that works with Bolt protocol 5.8+ when routing scheme is used. The cache is maintained by the driver internally and is not exposed in the API, its purpose is to reduce the number of Bolt exchanges when home database resolution is needed. --- .../NettyBoltConnectionProvider.java | 12 +- .../basicimpl/impl/BoltConnectionImpl.java | 15 +- .../impl/async/NetworkConnection.java | 7 + .../async/connection/BoltProtocolUtil.java | 4 +- .../async/connection/ChannelAttributes.java | 10 +- .../impl/handlers/BeginTxResponseHandler.java | 17 +- .../handlers/HelloV51ResponseHandler.java | 8 + .../impl/handlers/RunResponseHandler.java | 13 +- .../impl/messaging/BoltProtocol.java | 6 +- .../impl/messaging/v3/BoltProtocolV3.java | 9 +- .../impl/messaging/v58/BoltProtocolV58.java | 31 + .../bolt/basicimpl/impl/spi/Connection.java | 2 + .../connection/BoltProtocolUtilTest.java | 6 +- .../impl/messaging/v3/BoltProtocolV3Test.java | 11 +- .../impl/messaging/v4/BoltProtocolV4Test.java | 11 +- .../messaging/v41/BoltProtocolV41Test.java | 11 +- .../messaging/v42/BoltProtocolV42Test.java | 11 +- .../messaging/v43/BoltProtocolV43Test.java | 11 +- .../messaging/v44/BoltProtocolV44Test.java | 9 +- .../impl/messaging/v5/BoltProtocolV5Test.java | 11 +- .../messaging/v51/BoltProtocolV51Test.java | 11 +- .../messaging/v52/BoltProtocolV52Test.java | 11 +- .../messaging/v53/BoltProtocolV53Test.java | 11 +- .../messaging/v54/BoltProtocolV54Test.java | 11 +- .../messaging/v55/BoltProtocolV55Test.java | 11 +- .../messaging/v56/BoltProtocolV56Test.java | 11 +- .../messaging/v57/BoltProtocolV57Test.java | 11 +- .../messaging/v58/BoltProtocolV58Test.java | 783 ++++++++++++++++++ .../PooledBoltConnectionProvider.java | 16 +- .../pooledimpl/impl/PooledBoltConnection.java | 5 + .../PooledBoltConnectionProviderTest.java | 61 +- .../RoutedBoltConnectionProvider.java | 21 +- .../routedimpl/impl/RoutedBoltConnection.java | 5 + .../impl/cluster/RediscoveryImpl.java | 4 +- .../impl/cluster/RoutingTableHandler.java | 2 + .../impl/cluster/RoutingTableHandlerImpl.java | 8 + .../impl/cluster/RoutingTableRegistry.java | 3 +- .../cluster/RoutingTableRegistryImpl.java | 11 +- .../impl/cluster/RediscoveryTest.java | 4 +- .../impl/cluster/RoutingTableHandlerTest.java | 15 +- .../cluster/RoutingTableRegistryImplTest.java | 9 +- .../internal/bolt/api/BoltConnection.java | 2 + .../bolt/api/BoltConnectionProvider.java | 3 +- .../bolt/api/summary/BeginSummary.java | 6 +- .../internal/bolt/api/summary/RunSummary.java | 3 + .../neo4j/driver/internal/DriverFactory.java | 35 +- .../driver/internal/SessionFactoryImpl.java | 18 +- .../AdaptingDriverBoltConnection.java | 5 + .../AdaptingDriverBoltConnectionProvider.java | 6 +- .../adaptedbolt/DriverBoltConnection.java | 2 + .../DriverBoltConnectionProvider.java | 3 +- .../async/DelegatingBoltConnection.java | 5 + .../async/LeakLoggingNetworkSession.java | 7 +- .../driver/internal/async/NetworkSession.java | 293 ++++--- .../internal/async/UnmanagedTransaction.java | 15 +- .../boltlistener/BoltConnectionListener.java | 31 + .../boltlistener/ListeningBoltConnection.java | 204 +++++ .../ListeningBoltConnectionProvider.java | 108 +++ .../internal/cursor/ResultCursorImpl.java | 5 + .../internal/cursor/RxResultCursorImpl.java | 5 + .../homedb/DriverHomeDatabaseCacheKey.java | 21 + .../internal/homedb/HomeDatabaseCache.java | 31 + .../homedb/HomeDatabaseCacheImpl.java | 100 +++ .../internal/homedb/HomeDatabaseCacheKey.java | 43 + .../homedb/MapHomeDatabaseCacheKey.java | 21 + .../homedb/NoopHomeDatabaseCache.java | 36 + .../java/org/neo4j/driver/ParametersTest.java | 3 +- .../driver/internal/DriverFactoryTest.java | 9 +- .../driver/internal/InternalResultTest.java | 2 +- .../internal/InternalTransactionTest.java | 2 +- .../internal/SessionFactoryImplTest.java | 3 +- .../async/InternalAsyncSessionTest.java | 4 +- .../async/InternalAsyncTransactionTest.java | 2 +- .../async/LeakLoggingNetworkSessionTest.java | 5 +- .../internal/async/NetworkSessionTest.java | 39 +- .../async/UnmanagedTransactionTest.java | 14 + .../internal/cursor/ResultCursorImplTest.java | 3 +- .../homedb/HomeDatabaseCacheImplTest.java | 61 ++ .../org/neo4j/driver/testutil/TestUtil.java | 3 +- .../messages/requests/GetFeatures.java | 5 +- .../backend/messages/requests/NewDriver.java | 3 + .../backend/messages/requests/StartTest.java | 2 + 82 files changed, 2113 insertions(+), 294 deletions(-) create mode 100644 bolt-api-netty/src/main/java/org/neo4j/driver/internal/bolt/basicimpl/impl/messaging/v58/BoltProtocolV58.java create mode 100644 bolt-api-netty/src/test/java/org/neo4j/driver/internal/bolt/basicimpl/impl/messaging/v58/BoltProtocolV58Test.java create mode 100644 driver/src/main/java/org/neo4j/driver/internal/boltlistener/BoltConnectionListener.java create mode 100644 driver/src/main/java/org/neo4j/driver/internal/boltlistener/ListeningBoltConnection.java create mode 100644 driver/src/main/java/org/neo4j/driver/internal/boltlistener/ListeningBoltConnectionProvider.java create mode 100644 driver/src/main/java/org/neo4j/driver/internal/homedb/DriverHomeDatabaseCacheKey.java create mode 100644 driver/src/main/java/org/neo4j/driver/internal/homedb/HomeDatabaseCache.java create mode 100644 driver/src/main/java/org/neo4j/driver/internal/homedb/HomeDatabaseCacheImpl.java create mode 100644 driver/src/main/java/org/neo4j/driver/internal/homedb/HomeDatabaseCacheKey.java create mode 100644 driver/src/main/java/org/neo4j/driver/internal/homedb/MapHomeDatabaseCacheKey.java create mode 100644 driver/src/main/java/org/neo4j/driver/internal/homedb/NoopHomeDatabaseCache.java create mode 100644 driver/src/test/java/org/neo4j/driver/internal/homedb/HomeDatabaseCacheImplTest.java diff --git a/bolt-api-netty/src/main/java/org/neo4j/driver/internal/bolt/basicimpl/NettyBoltConnectionProvider.java b/bolt-api-netty/src/main/java/org/neo4j/driver/internal/bolt/basicimpl/NettyBoltConnectionProvider.java index ed71fc77d2..8290c3678e 100644 --- a/bolt-api-netty/src/main/java/org/neo4j/driver/internal/bolt/basicimpl/NettyBoltConnectionProvider.java +++ b/bolt-api-netty/src/main/java/org/neo4j/driver/internal/bolt/basicimpl/NettyBoltConnectionProvider.java @@ -116,7 +116,8 @@ public CompletionStage connect( String impersonatedUser, BoltProtocolVersion minVersion, NotificationConfig notificationConfig, - Consumer databaseNameConsumer) { + Consumer databaseNameConsumer, + Map additionalParameters) { synchronized (this) { if (closeFuture != null) { return CompletableFuture.failedFuture(new IllegalStateException("Connection provider is closed.")); @@ -189,7 +190,8 @@ public CompletionStage verifyConnectivity(SecurityPlan securityPlan, Map {}) + (ignored) -> {}, + Collections.emptyMap()) .thenCompose(BoltConnection::close); } @@ -204,7 +206,8 @@ public CompletionStage supportsMultiDb(SecurityPlan securityPlan, Map {}) + (ignored) -> {}, + Collections.emptyMap()) .thenCompose(boltConnection -> { var supports = boltConnection.protocolVersion().compareTo(BoltProtocolV4.VERSION) >= 0; return boltConnection.close().thenApply(ignored -> supports); @@ -222,7 +225,8 @@ public CompletionStage supportsSessionAuth(SecurityPlan securityPlan, M null, null, null, - (ignored) -> {}) + (ignored) -> {}, + Collections.emptyMap()) .thenCompose(boltConnection -> { var supports = BoltProtocolV51.VERSION.compareTo(boltConnection.protocolVersion()) <= 0; return boltConnection.close().thenApply(ignored -> supports); diff --git a/bolt-api-netty/src/main/java/org/neo4j/driver/internal/bolt/basicimpl/impl/BoltConnectionImpl.java b/bolt-api-netty/src/main/java/org/neo4j/driver/internal/bolt/basicimpl/impl/BoltConnectionImpl.java index df49d7b65b..4072d40a07 100644 --- a/bolt-api-netty/src/main/java/org/neo4j/driver/internal/bolt/basicimpl/impl/BoltConnectionImpl.java +++ b/bolt-api-netty/src/main/java/org/neo4j/driver/internal/bolt/basicimpl/impl/BoltConnectionImpl.java @@ -80,6 +80,7 @@ public final class BoltConnectionImpl implements BoltConnection { private final BoltServerAddress serverAddress; private final BoltProtocolVersion protocolVersion; private final boolean telemetrySupported; + private final boolean serverSideRouting; private final AtomicReference stateRef = new AtomicReference<>(BoltConnectionState.OPEN); private final AtomicReference> authDataRef; private final Map routingContext; @@ -104,6 +105,7 @@ public BoltConnectionImpl( this.serverAddress = Objects.requireNonNull(connection.serverAddress()); this.protocolVersion = Objects.requireNonNull(connection.protocol().version()); this.telemetrySupported = connection.isTelemetryEnabled(); + this.serverSideRouting = connection.isSsrEnabled(); this.authDataRef = new AtomicReference<>( CompletableFuture.completedFuture(new AuthDataImpl(authMap, latestAuthMillisFuture.join()))); this.valueFactory = Objects.requireNonNull(valueFactory); @@ -177,8 +179,8 @@ public void onError(Throwable throwable) { } @Override - public void onSummary(Void summary) { - handler.onBeginSummary(BeginSummaryImpl.INSTANCE); + public void onSummary(BeginSummary summary) { + handler.onBeginSummary(summary); } }, logging, @@ -520,6 +522,11 @@ public boolean telemetrySupported() { return telemetrySupported; } + @Override + public boolean serverSideRoutingEnabled() { + return serverSideRouting; + } + private CompletionStage executeInEventLoop(Runnable runnable) { var executeFuture = new CompletableFuture(); Runnable stageCompletingRunnable = () -> { @@ -720,10 +727,6 @@ private void runIgnoringError(Runnable runnable) { } } - private static class BeginSummaryImpl implements BeginSummary { - private static final BeginSummary INSTANCE = new BeginSummaryImpl(); - } - private static class TelemetrySummaryImpl implements TelemetrySummary { private static final TelemetrySummary INSTANCE = new TelemetrySummaryImpl(); } diff --git a/bolt-api-netty/src/main/java/org/neo4j/driver/internal/bolt/basicimpl/impl/async/NetworkConnection.java b/bolt-api-netty/src/main/java/org/neo4j/driver/internal/bolt/basicimpl/impl/async/NetworkConnection.java index 0033847d44..5d3876ac76 100644 --- a/bolt-api-netty/src/main/java/org/neo4j/driver/internal/bolt/basicimpl/impl/async/NetworkConnection.java +++ b/bolt-api-netty/src/main/java/org/neo4j/driver/internal/bolt/basicimpl/impl/async/NetworkConnection.java @@ -53,6 +53,7 @@ public class NetworkConnection implements Connection { private final String serverAgent; private final BoltServerAddress serverAddress; private final boolean telemetryEnabled; + private final boolean ssrEnabled; private final BoltProtocol protocol; private final Long connectionReadTimeout; @@ -67,6 +68,7 @@ public NetworkConnection(Channel channel, LoggingProvider logging) { this.serverAgent = ChannelAttributes.serverAgent(channel); this.serverAddress = ChannelAttributes.serverAddress(channel); this.telemetryEnabled = ChannelAttributes.telemetryEnabled(channel); + this.ssrEnabled = ChannelAttributes.ssrEnabled(channel); this.protocol = BoltProtocol.forChannel(channel); this.connectionReadTimeout = ChannelAttributes.connectionReadTimeout(channel).orElse(null); @@ -111,6 +113,11 @@ public boolean isTelemetryEnabled() { return telemetryEnabled; } + @Override + public boolean isSsrEnabled() { + return ssrEnabled; + } + @Override public String serverAgent() { return serverAgent; diff --git a/bolt-api-netty/src/main/java/org/neo4j/driver/internal/bolt/basicimpl/impl/async/connection/BoltProtocolUtil.java b/bolt-api-netty/src/main/java/org/neo4j/driver/internal/bolt/basicimpl/impl/async/connection/BoltProtocolUtil.java index bf44606aef..6ad5d02ec4 100644 --- a/bolt-api-netty/src/main/java/org/neo4j/driver/internal/bolt/basicimpl/impl/async/connection/BoltProtocolUtil.java +++ b/bolt-api-netty/src/main/java/org/neo4j/driver/internal/bolt/basicimpl/impl/async/connection/BoltProtocolUtil.java @@ -27,7 +27,7 @@ import org.neo4j.driver.internal.bolt.basicimpl.impl.messaging.v42.BoltProtocolV42; import org.neo4j.driver.internal.bolt.basicimpl.impl.messaging.v44.BoltProtocolV44; import org.neo4j.driver.internal.bolt.basicimpl.impl.messaging.v5.BoltProtocolV5; -import org.neo4j.driver.internal.bolt.basicimpl.impl.messaging.v57.BoltProtocolV57; +import org.neo4j.driver.internal.bolt.basicimpl.impl.messaging.v58.BoltProtocolV58; public final class BoltProtocolUtil { public static final int BOLT_MAGIC_PREAMBLE = 0x6060B017; @@ -39,7 +39,7 @@ public final class BoltProtocolUtil { private static final ByteBuf HANDSHAKE_BUF = unreleasableBuffer(copyInt( BOLT_MAGIC_PREAMBLE, - BoltProtocolV57.VERSION.toIntRange(BoltProtocolV5.VERSION), + BoltProtocolV58.VERSION.toIntRange(BoltProtocolV5.VERSION), BoltProtocolV44.VERSION.toIntRange(BoltProtocolV42.VERSION), BoltProtocolV41.VERSION.toInt(), BoltProtocolV3.VERSION.toInt())) diff --git a/bolt-api-netty/src/main/java/org/neo4j/driver/internal/bolt/basicimpl/impl/async/connection/ChannelAttributes.java b/bolt-api-netty/src/main/java/org/neo4j/driver/internal/bolt/basicimpl/impl/async/connection/ChannelAttributes.java index 6243e66265..20d5d6398d 100644 --- a/bolt-api-netty/src/main/java/org/neo4j/driver/internal/bolt/basicimpl/impl/async/connection/ChannelAttributes.java +++ b/bolt-api-netty/src/main/java/org/neo4j/driver/internal/bolt/basicimpl/impl/async/connection/ChannelAttributes.java @@ -45,8 +45,8 @@ public final class ChannelAttributes { // configuration hints provided by the server private static final AttributeKey CONNECTION_READ_TIMEOUT = newInstance("connectionReadTimeout"); - private static final AttributeKey TELEMETRY_ENABLED = newInstance("telemetryEnabled"); + private static final AttributeKey SSR_ENABLED = newInstance("ssr.enabled"); private ChannelAttributes() {} @@ -153,6 +153,14 @@ public static boolean telemetryEnabled(Channel channel) { return Optional.ofNullable(get(channel, TELEMETRY_ENABLED)).orElse(false); } + public static void setSsrEnabled(Channel channel, Boolean telemetryEnabled) { + setOnce(channel, SSR_ENABLED, telemetryEnabled); + } + + public static boolean ssrEnabled(Channel channel) { + return Optional.ofNullable(get(channel, SSR_ENABLED)).orElse(false); + } + private static T get(Channel channel, AttributeKey key) { return channel.attr(key).get(); } diff --git a/bolt-api-netty/src/main/java/org/neo4j/driver/internal/bolt/basicimpl/impl/handlers/BeginTxResponseHandler.java b/bolt-api-netty/src/main/java/org/neo4j/driver/internal/bolt/basicimpl/impl/handlers/BeginTxResponseHandler.java index cde25493e5..b5332dc454 100644 --- a/bolt-api-netty/src/main/java/org/neo4j/driver/internal/bolt/basicimpl/impl/handlers/BeginTxResponseHandler.java +++ b/bolt-api-netty/src/main/java/org/neo4j/driver/internal/bolt/basicimpl/impl/handlers/BeginTxResponseHandler.java @@ -20,20 +20,24 @@ import java.util.Arrays; import java.util.Map; +import java.util.Optional; import java.util.concurrent.CompletableFuture; +import org.neo4j.driver.internal.bolt.api.summary.BeginSummary; import org.neo4j.driver.internal.bolt.api.values.Value; import org.neo4j.driver.internal.bolt.basicimpl.impl.spi.ResponseHandler; public class BeginTxResponseHandler implements ResponseHandler { - private final CompletableFuture beginTxFuture; + private final CompletableFuture beginTxFuture; - public BeginTxResponseHandler(CompletableFuture beginTxFuture) { + public BeginTxResponseHandler(CompletableFuture beginTxFuture) { this.beginTxFuture = requireNonNull(beginTxFuture); } @Override public void onSuccess(Map metadata) { - beginTxFuture.complete(null); + var db = metadata.get("db"); + var databaseName = db != null ? db.asString() : null; + beginTxFuture.complete(new BeginSummaryImpl(databaseName)); } @Override @@ -46,4 +50,11 @@ public void onRecord(Value[] fields) { throw new UnsupportedOperationException( "Transaction begin is not expected to receive records: " + Arrays.toString(fields)); } + + private record BeginSummaryImpl(String database) implements BeginSummary { + @Override + public Optional databaseName() { + return Optional.ofNullable(database); + } + } } diff --git a/bolt-api-netty/src/main/java/org/neo4j/driver/internal/bolt/basicimpl/impl/handlers/HelloV51ResponseHandler.java b/bolt-api-netty/src/main/java/org/neo4j/driver/internal/bolt/basicimpl/impl/handlers/HelloV51ResponseHandler.java index 5162e5eac4..d2b6622435 100644 --- a/bolt-api-netty/src/main/java/org/neo4j/driver/internal/bolt/basicimpl/impl/handlers/HelloV51ResponseHandler.java +++ b/bolt-api-netty/src/main/java/org/neo4j/driver/internal/bolt/basicimpl/impl/handlers/HelloV51ResponseHandler.java @@ -19,6 +19,7 @@ import static org.neo4j.driver.internal.bolt.basicimpl.impl.async.connection.ChannelAttributes.setConnectionId; import static org.neo4j.driver.internal.bolt.basicimpl.impl.async.connection.ChannelAttributes.setConnectionReadTimeout; import static org.neo4j.driver.internal.bolt.basicimpl.impl.async.connection.ChannelAttributes.setServerAgent; +import static org.neo4j.driver.internal.bolt.basicimpl.impl.async.connection.ChannelAttributes.setSsrEnabled; import static org.neo4j.driver.internal.bolt.basicimpl.impl.async.connection.ChannelAttributes.setTelemetryEnabled; import static org.neo4j.driver.internal.bolt.basicimpl.impl.util.MetadataExtractor.extractServer; @@ -35,6 +36,7 @@ public class HelloV51ResponseHandler implements ResponseHandler { public static final String CONFIGURATION_HINTS_KEY = "hints"; public static final String CONNECTION_RECEIVE_TIMEOUT_SECONDS_KEY = "connection.recv_timeout_seconds"; public static final String TELEMETRY_ENABLED_KEY = "telemetry.enabled"; + public static final String SSR_ENABLED_KEY = "ssr.enabled"; private final Channel channel; private final CompletableFuture helloFuture; @@ -85,6 +87,12 @@ private void processConfigurationHints(Map metadata) { return !value.isNull() && value.asBoolean(); }) .ifPresent(telemetryEnabled -> setTelemetryEnabled(channel, telemetryEnabled)); + + getFromSupplierOrEmptyOnException(() -> { + var value = configurationHints.get(SSR_ENABLED_KEY); + return !value.isNull() && value.asBoolean(); + }) + .ifPresent(telemetryEnabled -> setSsrEnabled(channel, telemetryEnabled)); } } diff --git a/bolt-api-netty/src/main/java/org/neo4j/driver/internal/bolt/basicimpl/impl/handlers/RunResponseHandler.java b/bolt-api-netty/src/main/java/org/neo4j/driver/internal/bolt/basicimpl/impl/handlers/RunResponseHandler.java index 10ba283760..904b2fd4db 100644 --- a/bolt-api-netty/src/main/java/org/neo4j/driver/internal/bolt/basicimpl/impl/handlers/RunResponseHandler.java +++ b/bolt-api-netty/src/main/java/org/neo4j/driver/internal/bolt/basicimpl/impl/handlers/RunResponseHandler.java @@ -18,6 +18,7 @@ import java.util.List; import java.util.Map; +import java.util.Optional; import java.util.concurrent.CompletableFuture; import org.neo4j.driver.internal.bolt.api.summary.RunSummary; import org.neo4j.driver.internal.bolt.api.values.Value; @@ -38,8 +39,10 @@ public void onSuccess(Map metadata) { var queryKeys = metadataExtractor.extractQueryKeys(metadata); var resultAvailableAfter = metadataExtractor.extractResultAvailableAfter(metadata); var queryId = metadataExtractor.extractQueryId(metadata); + var db = metadata.get("db"); + var databaseName = db != null ? db.asString() : null; - runFuture.complete(new RunResponseImpl(queryId, queryKeys, resultAvailableAfter)); + runFuture.complete(new RunResponseImpl(queryId, queryKeys, resultAvailableAfter, databaseName)); } @Override @@ -52,5 +55,11 @@ public void onRecord(Value[] fields) { throw new UnsupportedOperationException(); } - private record RunResponseImpl(long queryId, List keys, long resultAvailableAfter) implements RunSummary {} + private record RunResponseImpl(long queryId, List keys, long resultAvailableAfter, String database) + implements RunSummary { + @Override + public Optional databaseName() { + return Optional.ofNullable(database); + } + } } diff --git a/bolt-api-netty/src/main/java/org/neo4j/driver/internal/bolt/basicimpl/impl/messaging/BoltProtocol.java b/bolt-api-netty/src/main/java/org/neo4j/driver/internal/bolt/basicimpl/impl/messaging/BoltProtocol.java index 334e511680..8da0890eec 100644 --- a/bolt-api-netty/src/main/java/org/neo4j/driver/internal/bolt/basicimpl/impl/messaging/BoltProtocol.java +++ b/bolt-api-netty/src/main/java/org/neo4j/driver/internal/bolt/basicimpl/impl/messaging/BoltProtocol.java @@ -34,6 +34,7 @@ import org.neo4j.driver.internal.bolt.api.RoutingContext; import org.neo4j.driver.internal.bolt.api.exception.BoltClientException; import org.neo4j.driver.internal.bolt.api.exception.BoltUnsupportedFeatureException; +import org.neo4j.driver.internal.bolt.api.summary.BeginSummary; import org.neo4j.driver.internal.bolt.api.summary.DiscardSummary; import org.neo4j.driver.internal.bolt.api.summary.RouteSummary; import org.neo4j.driver.internal.bolt.api.summary.RunSummary; @@ -53,6 +54,7 @@ import org.neo4j.driver.internal.bolt.basicimpl.impl.messaging.v55.BoltProtocolV55; import org.neo4j.driver.internal.bolt.basicimpl.impl.messaging.v56.BoltProtocolV56; import org.neo4j.driver.internal.bolt.basicimpl.impl.messaging.v57.BoltProtocolV57; +import org.neo4j.driver.internal.bolt.basicimpl.impl.messaging.v58.BoltProtocolV58; import org.neo4j.driver.internal.bolt.basicimpl.impl.spi.Connection; public interface BoltProtocol { @@ -90,7 +92,7 @@ CompletionStage beginTransaction( Map txMetadata, String txType, NotificationConfig notificationConfig, - MessageHandler handler, + MessageHandler handler, LoggingProvider logging, ValueFactory valueFactory); @@ -182,6 +184,8 @@ static BoltProtocol forVersion(BoltProtocolVersion version) { return BoltProtocolV56.INSTANCE; } else if (BoltProtocolV57.VERSION.equals(version)) { return BoltProtocolV57.INSTANCE; + } else if (BoltProtocolV58.VERSION.equals(version)) { + return BoltProtocolV58.INSTANCE; } throw new BoltClientException("Unknown protocol version: " + version); } diff --git a/bolt-api-netty/src/main/java/org/neo4j/driver/internal/bolt/basicimpl/impl/messaging/v3/BoltProtocolV3.java b/bolt-api-netty/src/main/java/org/neo4j/driver/internal/bolt/basicimpl/impl/messaging/v3/BoltProtocolV3.java index e17398e060..c5349504e6 100644 --- a/bolt-api-netty/src/main/java/org/neo4j/driver/internal/bolt/basicimpl/impl/messaging/v3/BoltProtocolV3.java +++ b/bolt-api-netty/src/main/java/org/neo4j/driver/internal/bolt/basicimpl/impl/messaging/v3/BoltProtocolV3.java @@ -42,6 +42,7 @@ import org.neo4j.driver.internal.bolt.api.RoutingContext; import org.neo4j.driver.internal.bolt.api.exception.BoltException; import org.neo4j.driver.internal.bolt.api.exception.BoltUnsupportedFeatureException; +import org.neo4j.driver.internal.bolt.api.summary.BeginSummary; import org.neo4j.driver.internal.bolt.api.summary.DiscardSummary; import org.neo4j.driver.internal.bolt.api.summary.PullSummary; import org.neo4j.driver.internal.bolt.api.summary.RouteSummary; @@ -249,7 +250,7 @@ public CompletionStage beginTransaction( Map txMetadata, String txType, NotificationConfig notificationConfig, - MessageHandler handler, + MessageHandler handler, LoggingProvider logging, ValueFactory valueFactory) { var exception = verifyNotificationConfigSupported(notificationConfig); @@ -262,7 +263,7 @@ public CompletionStage beginTransaction( return CompletableFuture.failedFuture(error); } - var beginTxFuture = new CompletableFuture(); + var beginTxFuture = new CompletableFuture(); var beginMessage = new BeginMessage( bookmarks, txTimeout, @@ -275,11 +276,11 @@ public CompletionStage beginTransaction( useLegacyNotifications(), logging, valueFactory); - beginTxFuture.whenComplete((ignored, throwable) -> { + beginTxFuture.whenComplete((summary, throwable) -> { if (throwable != null) { handler.onError(throwable); } else { - handler.onSummary(null); + handler.onSummary(summary); } }); return connection.write(beginMessage, new BeginTxResponseHandler(beginTxFuture)); diff --git a/bolt-api-netty/src/main/java/org/neo4j/driver/internal/bolt/basicimpl/impl/messaging/v58/BoltProtocolV58.java b/bolt-api-netty/src/main/java/org/neo4j/driver/internal/bolt/basicimpl/impl/messaging/v58/BoltProtocolV58.java new file mode 100644 index 0000000000..e0c77c673a --- /dev/null +++ b/bolt-api-netty/src/main/java/org/neo4j/driver/internal/bolt/basicimpl/impl/messaging/v58/BoltProtocolV58.java @@ -0,0 +1,31 @@ +/* + * Copyright (c) "Neo4j" + * Neo4j Sweden AB [https://neo4j.com] + * + * 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.bolt.basicimpl.impl.messaging.v58; + +import org.neo4j.driver.internal.bolt.api.BoltProtocolVersion; +import org.neo4j.driver.internal.bolt.basicimpl.impl.messaging.BoltProtocol; +import org.neo4j.driver.internal.bolt.basicimpl.impl.messaging.v57.BoltProtocolV57; + +public class BoltProtocolV58 extends BoltProtocolV57 { + public static final BoltProtocolVersion VERSION = new BoltProtocolVersion(5, 8); + public static final BoltProtocol INSTANCE = new BoltProtocolV58(); + + @Override + public BoltProtocolVersion version() { + return VERSION; + } +} diff --git a/bolt-api-netty/src/main/java/org/neo4j/driver/internal/bolt/basicimpl/impl/spi/Connection.java b/bolt-api-netty/src/main/java/org/neo4j/driver/internal/bolt/basicimpl/impl/spi/Connection.java index 321f2c3066..bd9dbb045d 100644 --- a/bolt-api-netty/src/main/java/org/neo4j/driver/internal/bolt/basicimpl/impl/spi/Connection.java +++ b/bolt-api-netty/src/main/java/org/neo4j/driver/internal/bolt/basicimpl/impl/spi/Connection.java @@ -35,6 +35,8 @@ public interface Connection { boolean isTelemetryEnabled(); + boolean isSsrEnabled(); + String serverAgent(); BoltServerAddress serverAddress(); diff --git a/bolt-api-netty/src/test/java/org/neo4j/driver/internal/bolt/basicimpl/impl/async/connection/BoltProtocolUtilTest.java b/bolt-api-netty/src/test/java/org/neo4j/driver/internal/bolt/basicimpl/impl/async/connection/BoltProtocolUtilTest.java index b95c632822..533faf6187 100644 --- a/bolt-api-netty/src/test/java/org/neo4j/driver/internal/bolt/basicimpl/impl/async/connection/BoltProtocolUtilTest.java +++ b/bolt-api-netty/src/test/java/org/neo4j/driver/internal/bolt/basicimpl/impl/async/connection/BoltProtocolUtilTest.java @@ -30,7 +30,7 @@ import org.neo4j.driver.internal.bolt.basicimpl.impl.messaging.v3.BoltProtocolV3; import org.neo4j.driver.internal.bolt.basicimpl.impl.messaging.v41.BoltProtocolV41; import org.neo4j.driver.internal.bolt.basicimpl.impl.messaging.v44.BoltProtocolV44; -import org.neo4j.driver.internal.bolt.basicimpl.impl.messaging.v57.BoltProtocolV57; +import org.neo4j.driver.internal.bolt.basicimpl.impl.messaging.v58.BoltProtocolV58; class BoltProtocolUtilTest { @Test @@ -38,7 +38,7 @@ void shouldReturnHandshakeBuf() { assertByteBufContains( handshakeBuf(), BOLT_MAGIC_PREAMBLE, - (7 << 16) | BoltProtocolV57.VERSION.toInt(), + (8 << 16) | BoltProtocolV58.VERSION.toInt(), (2 << 16) | BoltProtocolV44.VERSION.toInt(), BoltProtocolV41.VERSION.toInt(), BoltProtocolV3.VERSION.toInt()); @@ -46,7 +46,7 @@ void shouldReturnHandshakeBuf() { @Test void shouldReturnHandshakeString() { - assertEquals("[0x6060b017, 460549, 132100, 260, 3]", handshakeString()); + assertEquals("[0x6060b017, 526341, 132100, 260, 3]", handshakeString()); } @Test diff --git a/bolt-api-netty/src/test/java/org/neo4j/driver/internal/bolt/basicimpl/impl/messaging/v3/BoltProtocolV3Test.java b/bolt-api-netty/src/test/java/org/neo4j/driver/internal/bolt/basicimpl/impl/messaging/v3/BoltProtocolV3Test.java index 0081874961..efdd345cd7 100644 --- a/bolt-api-netty/src/test/java/org/neo4j/driver/internal/bolt/basicimpl/impl/messaging/v3/BoltProtocolV3Test.java +++ b/bolt-api-netty/src/test/java/org/neo4j/driver/internal/bolt/basicimpl/impl/messaging/v3/BoltProtocolV3Test.java @@ -58,6 +58,7 @@ import org.neo4j.driver.internal.bolt.api.AccessMode; import org.neo4j.driver.internal.bolt.api.RoutingContext; import org.neo4j.driver.internal.bolt.api.exception.BoltClientException; +import org.neo4j.driver.internal.bolt.api.summary.BeginSummary; import org.neo4j.driver.internal.bolt.api.summary.RouteSummary; import org.neo4j.driver.internal.bolt.api.summary.RunSummary; import org.neo4j.driver.internal.bolt.api.values.Value; @@ -261,7 +262,7 @@ void shouldBeginTransactionWithoutBookmark() { return expectedStage; }); @SuppressWarnings("unchecked") - var handler = (MessageHandler) mock(MessageHandler.class); + var handler = (MessageHandler) mock(MessageHandler.class); var stage = protocol.beginTransaction( connection, @@ -305,7 +306,7 @@ void shouldBeginTransactionWithBookmarks() { return expectedStage; }); @SuppressWarnings("unchecked") - var handler = (MessageHandler) mock(MessageHandler.class); + var handler = (MessageHandler) mock(MessageHandler.class); var bookmarks = Collections.singleton("neo4j:bookmark:v1:tx100"); var stage = protocol.beginTransaction( @@ -350,7 +351,7 @@ void shouldBeginTransactionWithConfig() { return expectedStage; }); @SuppressWarnings("unchecked") - var handler = (MessageHandler) mock(MessageHandler.class); + var handler = (MessageHandler) mock(MessageHandler.class); var stage = protocol.beginTransaction( connection, @@ -394,7 +395,7 @@ void shouldBeginTransactionWithBookmarksAndConfig() { return expectedStage; }); @SuppressWarnings("unchecked") - var handler = (MessageHandler) mock(MessageHandler.class); + var handler = (MessageHandler) mock(MessageHandler.class); var bookmarks = Collections.singleton("neo4j:bookmark:v1:tx4242"); var stage = protocol.beginTransaction( @@ -587,7 +588,7 @@ protected void testDatabaseNameSupport(boolean autoCommitTx) { return expectedStage; }); @SuppressWarnings("unchecked") - var handler = (MessageHandler) mock(MessageHandler.class); + var handler = (MessageHandler) mock(MessageHandler.class); var future = protocol.beginTransaction( connection, diff --git a/bolt-api-netty/src/test/java/org/neo4j/driver/internal/bolt/basicimpl/impl/messaging/v4/BoltProtocolV4Test.java b/bolt-api-netty/src/test/java/org/neo4j/driver/internal/bolt/basicimpl/impl/messaging/v4/BoltProtocolV4Test.java index 62ebcc4eba..78d9d97780 100644 --- a/bolt-api-netty/src/test/java/org/neo4j/driver/internal/bolt/basicimpl/impl/messaging/v4/BoltProtocolV4Test.java +++ b/bolt-api-netty/src/test/java/org/neo4j/driver/internal/bolt/basicimpl/impl/messaging/v4/BoltProtocolV4Test.java @@ -54,6 +54,7 @@ import org.neo4j.bolt.api.test.values.TestValueFactory; import org.neo4j.driver.internal.bolt.api.AccessMode; import org.neo4j.driver.internal.bolt.api.RoutingContext; +import org.neo4j.driver.internal.bolt.api.summary.BeginSummary; import org.neo4j.driver.internal.bolt.api.summary.RouteSummary; import org.neo4j.driver.internal.bolt.api.summary.RunSummary; import org.neo4j.driver.internal.bolt.api.values.Value; @@ -248,7 +249,7 @@ void shouldBeginTransactionWithoutBookmark() { return expectedStage; }); @SuppressWarnings("unchecked") - var handler = (MessageHandler) mock(MessageHandler.class); + var handler = (MessageHandler) mock(MessageHandler.class); var stage = protocol.beginTransaction( connection, @@ -292,7 +293,7 @@ void shouldBeginTransactionWithBookmarks() { return expectedStage; }); @SuppressWarnings("unchecked") - var handler = (MessageHandler) mock(MessageHandler.class); + var handler = (MessageHandler) mock(MessageHandler.class); var bookmarks = Collections.singleton("neo4j:bookmark:v1:tx100"); var stage = protocol.beginTransaction( @@ -337,7 +338,7 @@ void shouldBeginTransactionWithConfig() { return expectedStage; }); @SuppressWarnings("unchecked") - var handler = (MessageHandler) mock(MessageHandler.class); + var handler = (MessageHandler) mock(MessageHandler.class); var stage = protocol.beginTransaction( connection, @@ -381,7 +382,7 @@ void shouldBeginTransactionWithBookmarksAndConfig() { return expectedStage; }); @SuppressWarnings("unchecked") - var handler = (MessageHandler) mock(MessageHandler.class); + var handler = (MessageHandler) mock(MessageHandler.class); var bookmarks = Collections.singleton("neo4j:bookmark:v1:tx4242"); var stage = protocol.beginTransaction( @@ -842,7 +843,7 @@ private void testDatabaseNameSupport(boolean autoCommitTx) { return expectedStage; }); @SuppressWarnings("unchecked") - var handler = (MessageHandler) mock(MessageHandler.class); + var handler = (MessageHandler) mock(MessageHandler.class); var stage = protocol.beginTransaction( connection, diff --git a/bolt-api-netty/src/test/java/org/neo4j/driver/internal/bolt/basicimpl/impl/messaging/v41/BoltProtocolV41Test.java b/bolt-api-netty/src/test/java/org/neo4j/driver/internal/bolt/basicimpl/impl/messaging/v41/BoltProtocolV41Test.java index 858cd67592..1cf64de37b 100644 --- a/bolt-api-netty/src/test/java/org/neo4j/driver/internal/bolt/basicimpl/impl/messaging/v41/BoltProtocolV41Test.java +++ b/bolt-api-netty/src/test/java/org/neo4j/driver/internal/bolt/basicimpl/impl/messaging/v41/BoltProtocolV41Test.java @@ -52,6 +52,7 @@ import org.neo4j.bolt.api.test.values.TestValueFactory; import org.neo4j.driver.internal.bolt.api.AccessMode; import org.neo4j.driver.internal.bolt.api.RoutingContext; +import org.neo4j.driver.internal.bolt.api.summary.BeginSummary; import org.neo4j.driver.internal.bolt.api.summary.RunSummary; import org.neo4j.driver.internal.bolt.api.values.Value; import org.neo4j.driver.internal.bolt.api.values.ValueFactory; @@ -184,7 +185,7 @@ void shouldBeginTransactionWithoutBookmark() { return expectedStage; }); @SuppressWarnings("unchecked") - var handler = (MessageHandler) mock(MessageHandler.class); + var handler = (MessageHandler) mock(MessageHandler.class); var stage = protocol.beginTransaction( connection, @@ -228,7 +229,7 @@ void shouldBeginTransactionWithBookmarks() { return expectedStage; }); @SuppressWarnings("unchecked") - var handler = (MessageHandler) mock(MessageHandler.class); + var handler = (MessageHandler) mock(MessageHandler.class); var bookmarks = Collections.singleton("neo4j:bookmark:v1:tx100"); var stage = protocol.beginTransaction( @@ -273,7 +274,7 @@ void shouldBeginTransactionWithConfig() { return expectedStage; }); @SuppressWarnings("unchecked") - var handler = (MessageHandler) mock(MessageHandler.class); + var handler = (MessageHandler) mock(MessageHandler.class); var stage = protocol.beginTransaction( connection, @@ -317,7 +318,7 @@ void shouldBeginTransactionWithBookmarksAndConfig() { return expectedStage; }); @SuppressWarnings("unchecked") - var handler = (MessageHandler) mock(MessageHandler.class); + var handler = (MessageHandler) mock(MessageHandler.class); var bookmarks = Collections.singleton("neo4j:bookmark:v1:tx4242"); var stage = protocol.beginTransaction( @@ -773,7 +774,7 @@ private void testDatabaseNameSupport(boolean autoCommitTx) { return expectedStage; }); @SuppressWarnings("unchecked") - var handler = (MessageHandler) mock(MessageHandler.class); + var handler = (MessageHandler) mock(MessageHandler.class); var stage = protocol.beginTransaction( connection, diff --git a/bolt-api-netty/src/test/java/org/neo4j/driver/internal/bolt/basicimpl/impl/messaging/v42/BoltProtocolV42Test.java b/bolt-api-netty/src/test/java/org/neo4j/driver/internal/bolt/basicimpl/impl/messaging/v42/BoltProtocolV42Test.java index 82e7d97780..2749a5f0ae 100644 --- a/bolt-api-netty/src/test/java/org/neo4j/driver/internal/bolt/basicimpl/impl/messaging/v42/BoltProtocolV42Test.java +++ b/bolt-api-netty/src/test/java/org/neo4j/driver/internal/bolt/basicimpl/impl/messaging/v42/BoltProtocolV42Test.java @@ -52,6 +52,7 @@ import org.neo4j.bolt.api.test.values.TestValueFactory; import org.neo4j.driver.internal.bolt.api.AccessMode; import org.neo4j.driver.internal.bolt.api.RoutingContext; +import org.neo4j.driver.internal.bolt.api.summary.BeginSummary; import org.neo4j.driver.internal.bolt.api.summary.RunSummary; import org.neo4j.driver.internal.bolt.api.values.Value; import org.neo4j.driver.internal.bolt.api.values.ValueFactory; @@ -184,7 +185,7 @@ void shouldBeginTransactionWithoutBookmark() { return expectedStage; }); @SuppressWarnings("unchecked") - var handler = (MessageHandler) mock(MessageHandler.class); + var handler = (MessageHandler) mock(MessageHandler.class); var stage = protocol.beginTransaction( connection, @@ -228,7 +229,7 @@ void shouldBeginTransactionWithBookmarks() { return expectedStage; }); @SuppressWarnings("unchecked") - var handler = (MessageHandler) mock(MessageHandler.class); + var handler = (MessageHandler) mock(MessageHandler.class); var bookmarks = Collections.singleton("neo4j:bookmark:v1:tx100"); var stage = protocol.beginTransaction( @@ -273,7 +274,7 @@ void shouldBeginTransactionWithConfig() { return expectedStage; }); @SuppressWarnings("unchecked") - var handler = (MessageHandler) mock(MessageHandler.class); + var handler = (MessageHandler) mock(MessageHandler.class); var stage = protocol.beginTransaction( connection, @@ -317,7 +318,7 @@ void shouldBeginTransactionWithBookmarksAndConfig() { return expectedStage; }); @SuppressWarnings("unchecked") - var handler = (MessageHandler) mock(MessageHandler.class); + var handler = (MessageHandler) mock(MessageHandler.class); var bookmarks = Collections.singleton("neo4j:bookmark:v1:tx4242"); var stage = protocol.beginTransaction( @@ -773,7 +774,7 @@ private void testDatabaseNameSupport(boolean autoCommitTx) { return expectedStage; }); @SuppressWarnings("unchecked") - var handler = (MessageHandler) mock(MessageHandler.class); + var handler = (MessageHandler) mock(MessageHandler.class); var stage = protocol.beginTransaction( connection, diff --git a/bolt-api-netty/src/test/java/org/neo4j/driver/internal/bolt/basicimpl/impl/messaging/v43/BoltProtocolV43Test.java b/bolt-api-netty/src/test/java/org/neo4j/driver/internal/bolt/basicimpl/impl/messaging/v43/BoltProtocolV43Test.java index 2eb9610ace..c45176caa3 100644 --- a/bolt-api-netty/src/test/java/org/neo4j/driver/internal/bolt/basicimpl/impl/messaging/v43/BoltProtocolV43Test.java +++ b/bolt-api-netty/src/test/java/org/neo4j/driver/internal/bolt/basicimpl/impl/messaging/v43/BoltProtocolV43Test.java @@ -54,6 +54,7 @@ import org.neo4j.bolt.api.test.values.TestValueFactory; import org.neo4j.driver.internal.bolt.api.AccessMode; import org.neo4j.driver.internal.bolt.api.RoutingContext; +import org.neo4j.driver.internal.bolt.api.summary.BeginSummary; import org.neo4j.driver.internal.bolt.api.summary.RouteSummary; import org.neo4j.driver.internal.bolt.api.summary.RunSummary; import org.neo4j.driver.internal.bolt.api.values.Value; @@ -249,7 +250,7 @@ void shouldBeginTransactionWithoutBookmark() { return expectedStage; }); @SuppressWarnings("unchecked") - var handler = (MessageHandler) mock(MessageHandler.class); + var handler = (MessageHandler) mock(MessageHandler.class); var stage = protocol.beginTransaction( connection, @@ -293,7 +294,7 @@ void shouldBeginTransactionWithBookmarks() { return expectedStage; }); @SuppressWarnings("unchecked") - var handler = (MessageHandler) mock(MessageHandler.class); + var handler = (MessageHandler) mock(MessageHandler.class); var bookmarks = Collections.singleton("neo4j:bookmark:v1:tx100"); var stage = protocol.beginTransaction( @@ -338,7 +339,7 @@ void shouldBeginTransactionWithConfig() { return expectedStage; }); @SuppressWarnings("unchecked") - var handler = (MessageHandler) mock(MessageHandler.class); + var handler = (MessageHandler) mock(MessageHandler.class); var stage = protocol.beginTransaction( connection, @@ -382,7 +383,7 @@ void shouldBeginTransactionWithBookmarksAndConfig() { return expectedStage; }); @SuppressWarnings("unchecked") - var handler = (MessageHandler) mock(MessageHandler.class); + var handler = (MessageHandler) mock(MessageHandler.class); var bookmarks = Collections.singleton("neo4j:bookmark:v1:tx4242"); var stage = protocol.beginTransaction( @@ -838,7 +839,7 @@ private void testDatabaseNameSupport(boolean autoCommitTx) { return expectedStage; }); @SuppressWarnings("unchecked") - var handler = (MessageHandler) mock(MessageHandler.class); + var handler = (MessageHandler) mock(MessageHandler.class); var stage = protocol.beginTransaction( connection, diff --git a/bolt-api-netty/src/test/java/org/neo4j/driver/internal/bolt/basicimpl/impl/messaging/v44/BoltProtocolV44Test.java b/bolt-api-netty/src/test/java/org/neo4j/driver/internal/bolt/basicimpl/impl/messaging/v44/BoltProtocolV44Test.java index 3f8d5c3763..437089a7c8 100644 --- a/bolt-api-netty/src/test/java/org/neo4j/driver/internal/bolt/basicimpl/impl/messaging/v44/BoltProtocolV44Test.java +++ b/bolt-api-netty/src/test/java/org/neo4j/driver/internal/bolt/basicimpl/impl/messaging/v44/BoltProtocolV44Test.java @@ -52,6 +52,7 @@ import org.neo4j.bolt.api.test.values.TestValueFactory; import org.neo4j.driver.internal.bolt.api.AccessMode; import org.neo4j.driver.internal.bolt.api.RoutingContext; +import org.neo4j.driver.internal.bolt.api.summary.BeginSummary; import org.neo4j.driver.internal.bolt.api.summary.RunSummary; import org.neo4j.driver.internal.bolt.api.values.Value; import org.neo4j.driver.internal.bolt.api.values.ValueFactory; @@ -183,7 +184,7 @@ void shouldBeginTransactionWithoutBookmark() { return expectedStage; }); @SuppressWarnings("unchecked") - var handler = (MessageHandler) mock(MessageHandler.class); + var handler = (MessageHandler) mock(MessageHandler.class); var stage = protocol.beginTransaction( connection, @@ -227,7 +228,7 @@ void shouldBeginTransactionWithBookmarks() { return expectedStage; }); @SuppressWarnings("unchecked") - var handler = (MessageHandler) mock(MessageHandler.class); + var handler = (MessageHandler) mock(MessageHandler.class); var bookmarks = Collections.singleton("neo4j:bookmark:v1:tx100"); var stage = protocol.beginTransaction( @@ -272,7 +273,7 @@ void shouldBeginTransactionWithConfig() { return expectedStage; }); @SuppressWarnings("unchecked") - var handler = (MessageHandler) mock(MessageHandler.class); + var handler = (MessageHandler) mock(MessageHandler.class); var stage = protocol.beginTransaction( connection, @@ -727,7 +728,7 @@ private void testDatabaseNameSupport(boolean autoCommitTx) { return expectedStage; }); @SuppressWarnings("unchecked") - var handler = (MessageHandler) mock(MessageHandler.class); + var handler = (MessageHandler) mock(MessageHandler.class); var stage = protocol.beginTransaction( connection, diff --git a/bolt-api-netty/src/test/java/org/neo4j/driver/internal/bolt/basicimpl/impl/messaging/v5/BoltProtocolV5Test.java b/bolt-api-netty/src/test/java/org/neo4j/driver/internal/bolt/basicimpl/impl/messaging/v5/BoltProtocolV5Test.java index d1b0761f99..e40ee3047f 100644 --- a/bolt-api-netty/src/test/java/org/neo4j/driver/internal/bolt/basicimpl/impl/messaging/v5/BoltProtocolV5Test.java +++ b/bolt-api-netty/src/test/java/org/neo4j/driver/internal/bolt/basicimpl/impl/messaging/v5/BoltProtocolV5Test.java @@ -52,6 +52,7 @@ import org.neo4j.bolt.api.test.values.TestValueFactory; import org.neo4j.driver.internal.bolt.api.AccessMode; import org.neo4j.driver.internal.bolt.api.RoutingContext; +import org.neo4j.driver.internal.bolt.api.summary.BeginSummary; import org.neo4j.driver.internal.bolt.api.summary.RunSummary; import org.neo4j.driver.internal.bolt.api.values.Value; import org.neo4j.driver.internal.bolt.api.values.ValueFactory; @@ -183,7 +184,7 @@ void shouldBeginTransactionWithoutBookmark() { return expectedStage; }); @SuppressWarnings("unchecked") - var handler = (MessageHandler) mock(MessageHandler.class); + var handler = (MessageHandler) mock(MessageHandler.class); var stage = protocol.beginTransaction( connection, @@ -227,7 +228,7 @@ void shouldBeginTransactionWithBookmarks() { return expectedStage; }); @SuppressWarnings("unchecked") - var handler = (MessageHandler) mock(MessageHandler.class); + var handler = (MessageHandler) mock(MessageHandler.class); var bookmarks = Collections.singleton("neo4j:bookmark:v1:tx100"); var stage = protocol.beginTransaction( @@ -272,7 +273,7 @@ void shouldBeginTransactionWithConfig() { return expectedStage; }); @SuppressWarnings("unchecked") - var handler = (MessageHandler) mock(MessageHandler.class); + var handler = (MessageHandler) mock(MessageHandler.class); var stage = protocol.beginTransaction( connection, @@ -316,7 +317,7 @@ void shouldBeginTransactionWithBookmarksAndConfig() { return expectedStage; }); @SuppressWarnings("unchecked") - var handler = (MessageHandler) mock(MessageHandler.class); + var handler = (MessageHandler) mock(MessageHandler.class); var bookmarks = Collections.singleton("neo4j:bookmark:v1:tx4242"); var stage = protocol.beginTransaction( @@ -772,7 +773,7 @@ private void testDatabaseNameSupport(boolean autoCommitTx) { return expectedStage; }); @SuppressWarnings("unchecked") - var handler = (MessageHandler) mock(MessageHandler.class); + var handler = (MessageHandler) mock(MessageHandler.class); var stage = protocol.beginTransaction( connection, diff --git a/bolt-api-netty/src/test/java/org/neo4j/driver/internal/bolt/basicimpl/impl/messaging/v51/BoltProtocolV51Test.java b/bolt-api-netty/src/test/java/org/neo4j/driver/internal/bolt/basicimpl/impl/messaging/v51/BoltProtocolV51Test.java index c71586ab4d..3c21b29f2d 100644 --- a/bolt-api-netty/src/test/java/org/neo4j/driver/internal/bolt/basicimpl/impl/messaging/v51/BoltProtocolV51Test.java +++ b/bolt-api-netty/src/test/java/org/neo4j/driver/internal/bolt/basicimpl/impl/messaging/v51/BoltProtocolV51Test.java @@ -52,6 +52,7 @@ import org.neo4j.bolt.api.test.values.TestValueFactory; import org.neo4j.driver.internal.bolt.api.AccessMode; import org.neo4j.driver.internal.bolt.api.RoutingContext; +import org.neo4j.driver.internal.bolt.api.summary.BeginSummary; import org.neo4j.driver.internal.bolt.api.summary.RunSummary; import org.neo4j.driver.internal.bolt.api.values.Value; import org.neo4j.driver.internal.bolt.api.values.ValueFactory; @@ -159,7 +160,7 @@ void shouldBeginTransactionWithoutBookmark() { return expectedStage; }); @SuppressWarnings("unchecked") - var handler = (MessageHandler) mock(MessageHandler.class); + var handler = (MessageHandler) mock(MessageHandler.class); var stage = protocol.beginTransaction( connection, @@ -203,7 +204,7 @@ void shouldBeginTransactionWithBookmarks() { return expectedStage; }); @SuppressWarnings("unchecked") - var handler = (MessageHandler) mock(MessageHandler.class); + var handler = (MessageHandler) mock(MessageHandler.class); var bookmarks = Collections.singleton("neo4j:bookmark:v1:tx100"); var stage = protocol.beginTransaction( @@ -248,7 +249,7 @@ void shouldBeginTransactionWithConfig() { return expectedStage; }); @SuppressWarnings("unchecked") - var handler = (MessageHandler) mock(MessageHandler.class); + var handler = (MessageHandler) mock(MessageHandler.class); var stage = protocol.beginTransaction( connection, @@ -292,7 +293,7 @@ void shouldBeginTransactionWithBookmarksAndConfig() { return expectedStage; }); @SuppressWarnings("unchecked") - var handler = (MessageHandler) mock(MessageHandler.class); + var handler = (MessageHandler) mock(MessageHandler.class); var bookmarks = Collections.singleton("neo4j:bookmark:v1:tx4242"); var stage = protocol.beginTransaction( @@ -748,7 +749,7 @@ private void testDatabaseNameSupport(boolean autoCommitTx) { return expectedStage; }); @SuppressWarnings("unchecked") - var handler = (MessageHandler) mock(MessageHandler.class); + var handler = (MessageHandler) mock(MessageHandler.class); var stage = protocol.beginTransaction( connection, diff --git a/bolt-api-netty/src/test/java/org/neo4j/driver/internal/bolt/basicimpl/impl/messaging/v52/BoltProtocolV52Test.java b/bolt-api-netty/src/test/java/org/neo4j/driver/internal/bolt/basicimpl/impl/messaging/v52/BoltProtocolV52Test.java index 89bab53710..d4d466163e 100644 --- a/bolt-api-netty/src/test/java/org/neo4j/driver/internal/bolt/basicimpl/impl/messaging/v52/BoltProtocolV52Test.java +++ b/bolt-api-netty/src/test/java/org/neo4j/driver/internal/bolt/basicimpl/impl/messaging/v52/BoltProtocolV52Test.java @@ -52,6 +52,7 @@ import org.neo4j.bolt.api.test.values.TestValueFactory; import org.neo4j.driver.internal.bolt.api.AccessMode; import org.neo4j.driver.internal.bolt.api.RoutingContext; +import org.neo4j.driver.internal.bolt.api.summary.BeginSummary; import org.neo4j.driver.internal.bolt.api.summary.RunSummary; import org.neo4j.driver.internal.bolt.api.values.Value; import org.neo4j.driver.internal.bolt.api.values.ValueFactory; @@ -160,7 +161,7 @@ void shouldBeginTransactionWithoutBookmark() { return expectedStage; }); @SuppressWarnings("unchecked") - var handler = (MessageHandler) mock(MessageHandler.class); + var handler = (MessageHandler) mock(MessageHandler.class); var stage = protocol.beginTransaction( connection, @@ -204,7 +205,7 @@ void shouldBeginTransactionWithBookmarks() { return expectedStage; }); @SuppressWarnings("unchecked") - var handler = (MessageHandler) mock(MessageHandler.class); + var handler = (MessageHandler) mock(MessageHandler.class); var bookmarks = Collections.singleton("neo4j:bookmark:v1:tx100"); var stage = protocol.beginTransaction( @@ -249,7 +250,7 @@ void shouldBeginTransactionWithConfig() { return expectedStage; }); @SuppressWarnings("unchecked") - var handler = (MessageHandler) mock(MessageHandler.class); + var handler = (MessageHandler) mock(MessageHandler.class); var stage = protocol.beginTransaction( connection, @@ -293,7 +294,7 @@ void shouldBeginTransactionWithBookmarksAndConfig() { return expectedStage; }); @SuppressWarnings("unchecked") - var handler = (MessageHandler) mock(MessageHandler.class); + var handler = (MessageHandler) mock(MessageHandler.class); var bookmarks = Collections.singleton("neo4j:bookmark:v1:tx4242"); var stage = protocol.beginTransaction( @@ -749,7 +750,7 @@ private void testDatabaseNameSupport(boolean autoCommitTx) { return expectedStage; }); @SuppressWarnings("unchecked") - var handler = (MessageHandler) mock(MessageHandler.class); + var handler = (MessageHandler) mock(MessageHandler.class); var stage = protocol.beginTransaction( connection, diff --git a/bolt-api-netty/src/test/java/org/neo4j/driver/internal/bolt/basicimpl/impl/messaging/v53/BoltProtocolV53Test.java b/bolt-api-netty/src/test/java/org/neo4j/driver/internal/bolt/basicimpl/impl/messaging/v53/BoltProtocolV53Test.java index 4452f265c7..d9d35eddb4 100644 --- a/bolt-api-netty/src/test/java/org/neo4j/driver/internal/bolt/basicimpl/impl/messaging/v53/BoltProtocolV53Test.java +++ b/bolt-api-netty/src/test/java/org/neo4j/driver/internal/bolt/basicimpl/impl/messaging/v53/BoltProtocolV53Test.java @@ -52,6 +52,7 @@ import org.neo4j.bolt.api.test.values.TestValueFactory; import org.neo4j.driver.internal.bolt.api.AccessMode; import org.neo4j.driver.internal.bolt.api.RoutingContext; +import org.neo4j.driver.internal.bolt.api.summary.BeginSummary; import org.neo4j.driver.internal.bolt.api.summary.RunSummary; import org.neo4j.driver.internal.bolt.api.values.Value; import org.neo4j.driver.internal.bolt.api.values.ValueFactory; @@ -160,7 +161,7 @@ void shouldBeginTransactionWithoutBookmark() { return expectedStage; }); @SuppressWarnings("unchecked") - var handler = (MessageHandler) mock(MessageHandler.class); + var handler = (MessageHandler) mock(MessageHandler.class); var stage = protocol.beginTransaction( connection, @@ -204,7 +205,7 @@ void shouldBeginTransactionWithBookmarks() { return expectedStage; }); @SuppressWarnings("unchecked") - var handler = (MessageHandler) mock(MessageHandler.class); + var handler = (MessageHandler) mock(MessageHandler.class); var bookmarks = Collections.singleton("neo4j:bookmark:v1:tx100"); var stage = protocol.beginTransaction( @@ -249,7 +250,7 @@ void shouldBeginTransactionWithConfig() { return expectedStage; }); @SuppressWarnings("unchecked") - var handler = (MessageHandler) mock(MessageHandler.class); + var handler = (MessageHandler) mock(MessageHandler.class); var stage = protocol.beginTransaction( connection, @@ -293,7 +294,7 @@ void shouldBeginTransactionWithBookmarksAndConfig() { return expectedStage; }); @SuppressWarnings("unchecked") - var handler = (MessageHandler) mock(MessageHandler.class); + var handler = (MessageHandler) mock(MessageHandler.class); var bookmarks = Collections.singleton("neo4j:bookmark:v1:tx4242"); var stage = protocol.beginTransaction( @@ -749,7 +750,7 @@ private void testDatabaseNameSupport(boolean autoCommitTx) { return expectedStage; }); @SuppressWarnings("unchecked") - var handler = (MessageHandler) mock(MessageHandler.class); + var handler = (MessageHandler) mock(MessageHandler.class); var stage = protocol.beginTransaction( connection, diff --git a/bolt-api-netty/src/test/java/org/neo4j/driver/internal/bolt/basicimpl/impl/messaging/v54/BoltProtocolV54Test.java b/bolt-api-netty/src/test/java/org/neo4j/driver/internal/bolt/basicimpl/impl/messaging/v54/BoltProtocolV54Test.java index df628fc187..b354e0c497 100644 --- a/bolt-api-netty/src/test/java/org/neo4j/driver/internal/bolt/basicimpl/impl/messaging/v54/BoltProtocolV54Test.java +++ b/bolt-api-netty/src/test/java/org/neo4j/driver/internal/bolt/basicimpl/impl/messaging/v54/BoltProtocolV54Test.java @@ -52,6 +52,7 @@ import org.neo4j.bolt.api.test.values.TestValueFactory; import org.neo4j.driver.internal.bolt.api.AccessMode; import org.neo4j.driver.internal.bolt.api.RoutingContext; +import org.neo4j.driver.internal.bolt.api.summary.BeginSummary; import org.neo4j.driver.internal.bolt.api.summary.RunSummary; import org.neo4j.driver.internal.bolt.api.values.Value; import org.neo4j.driver.internal.bolt.api.values.ValueFactory; @@ -160,7 +161,7 @@ void shouldBeginTransactionWithoutBookmark() { return expectedStage; }); @SuppressWarnings("unchecked") - var handler = (MessageHandler) mock(MessageHandler.class); + var handler = (MessageHandler) mock(MessageHandler.class); var stage = protocol.beginTransaction( connection, @@ -204,7 +205,7 @@ void shouldBeginTransactionWithBookmarks() { return expectedStage; }); @SuppressWarnings("unchecked") - var handler = (MessageHandler) mock(MessageHandler.class); + var handler = (MessageHandler) mock(MessageHandler.class); var bookmarks = Collections.singleton("neo4j:bookmark:v1:tx100"); var stage = protocol.beginTransaction( @@ -249,7 +250,7 @@ void shouldBeginTransactionWithConfig() { return expectedStage; }); @SuppressWarnings("unchecked") - var handler = (MessageHandler) mock(MessageHandler.class); + var handler = (MessageHandler) mock(MessageHandler.class); var stage = protocol.beginTransaction( connection, @@ -293,7 +294,7 @@ void shouldBeginTransactionWithBookmarksAndConfig() { return expectedStage; }); @SuppressWarnings("unchecked") - var handler = (MessageHandler) mock(MessageHandler.class); + var handler = (MessageHandler) mock(MessageHandler.class); var bookmarks = Collections.singleton("neo4j:bookmark:v1:tx4242"); var stage = protocol.beginTransaction( @@ -750,7 +751,7 @@ private void testDatabaseNameSupport(boolean autoCommitTx) { return expectedStage; }); @SuppressWarnings("unchecked") - var handler = (MessageHandler) mock(MessageHandler.class); + var handler = (MessageHandler) mock(MessageHandler.class); var stage = protocol.beginTransaction( connection, diff --git a/bolt-api-netty/src/test/java/org/neo4j/driver/internal/bolt/basicimpl/impl/messaging/v55/BoltProtocolV55Test.java b/bolt-api-netty/src/test/java/org/neo4j/driver/internal/bolt/basicimpl/impl/messaging/v55/BoltProtocolV55Test.java index a2d5f673bd..f17664848c 100644 --- a/bolt-api-netty/src/test/java/org/neo4j/driver/internal/bolt/basicimpl/impl/messaging/v55/BoltProtocolV55Test.java +++ b/bolt-api-netty/src/test/java/org/neo4j/driver/internal/bolt/basicimpl/impl/messaging/v55/BoltProtocolV55Test.java @@ -52,6 +52,7 @@ import org.neo4j.bolt.api.test.values.TestValueFactory; import org.neo4j.driver.internal.bolt.api.AccessMode; import org.neo4j.driver.internal.bolt.api.RoutingContext; +import org.neo4j.driver.internal.bolt.api.summary.BeginSummary; import org.neo4j.driver.internal.bolt.api.summary.RunSummary; import org.neo4j.driver.internal.bolt.api.values.Value; import org.neo4j.driver.internal.bolt.api.values.ValueFactory; @@ -161,7 +162,7 @@ void shouldBeginTransactionWithoutBookmark() { return expectedStage; }); @SuppressWarnings("unchecked") - var handler = (MessageHandler) mock(MessageHandler.class); + var handler = (MessageHandler) mock(MessageHandler.class); var stage = protocol.beginTransaction( connection, @@ -205,7 +206,7 @@ void shouldBeginTransactionWithBookmarks() { return expectedStage; }); @SuppressWarnings("unchecked") - var handler = (MessageHandler) mock(MessageHandler.class); + var handler = (MessageHandler) mock(MessageHandler.class); var bookmarks = Collections.singleton("neo4j:bookmark:v1:tx100"); var stage = protocol.beginTransaction( @@ -250,7 +251,7 @@ void shouldBeginTransactionWithConfig() { return expectedStage; }); @SuppressWarnings("unchecked") - var handler = (MessageHandler) mock(MessageHandler.class); + var handler = (MessageHandler) mock(MessageHandler.class); var stage = protocol.beginTransaction( connection, @@ -294,7 +295,7 @@ void shouldBeginTransactionWithBookmarksAndConfig() { return expectedStage; }); @SuppressWarnings("unchecked") - var handler = (MessageHandler) mock(MessageHandler.class); + var handler = (MessageHandler) mock(MessageHandler.class); var bookmarks = Collections.singleton("neo4j:bookmark:v1:tx4242"); var stage = protocol.beginTransaction( @@ -751,7 +752,7 @@ private void testDatabaseNameSupport(boolean autoCommitTx) { return expectedStage; }); @SuppressWarnings("unchecked") - var handler = (MessageHandler) mock(MessageHandler.class); + var handler = (MessageHandler) mock(MessageHandler.class); var stage = protocol.beginTransaction( connection, diff --git a/bolt-api-netty/src/test/java/org/neo4j/driver/internal/bolt/basicimpl/impl/messaging/v56/BoltProtocolV56Test.java b/bolt-api-netty/src/test/java/org/neo4j/driver/internal/bolt/basicimpl/impl/messaging/v56/BoltProtocolV56Test.java index aa0db0c9b5..58c7e16a30 100644 --- a/bolt-api-netty/src/test/java/org/neo4j/driver/internal/bolt/basicimpl/impl/messaging/v56/BoltProtocolV56Test.java +++ b/bolt-api-netty/src/test/java/org/neo4j/driver/internal/bolt/basicimpl/impl/messaging/v56/BoltProtocolV56Test.java @@ -52,6 +52,7 @@ import org.neo4j.bolt.api.test.values.TestValueFactory; import org.neo4j.driver.internal.bolt.api.AccessMode; import org.neo4j.driver.internal.bolt.api.RoutingContext; +import org.neo4j.driver.internal.bolt.api.summary.BeginSummary; import org.neo4j.driver.internal.bolt.api.summary.RunSummary; import org.neo4j.driver.internal.bolt.api.values.Value; import org.neo4j.driver.internal.bolt.api.values.ValueFactory; @@ -161,7 +162,7 @@ void shouldBeginTransactionWithoutBookmark() { return expectedStage; }); @SuppressWarnings("unchecked") - var handler = (MessageHandler) mock(MessageHandler.class); + var handler = (MessageHandler) mock(MessageHandler.class); var stage = protocol.beginTransaction( connection, @@ -205,7 +206,7 @@ void shouldBeginTransactionWithBookmarks() { return expectedStage; }); @SuppressWarnings("unchecked") - var handler = (MessageHandler) mock(MessageHandler.class); + var handler = (MessageHandler) mock(MessageHandler.class); var bookmarks = Collections.singleton("neo4j:bookmark:v1:tx100"); var stage = protocol.beginTransaction( @@ -250,7 +251,7 @@ void shouldBeginTransactionWithConfig() { return expectedStage; }); @SuppressWarnings("unchecked") - var handler = (MessageHandler) mock(MessageHandler.class); + var handler = (MessageHandler) mock(MessageHandler.class); var stage = protocol.beginTransaction( connection, @@ -294,7 +295,7 @@ void shouldBeginTransactionWithBookmarksAndConfig() { return expectedStage; }); @SuppressWarnings("unchecked") - var handler = (MessageHandler) mock(MessageHandler.class); + var handler = (MessageHandler) mock(MessageHandler.class); var bookmarks = Collections.singleton("neo4j:bookmark:v1:tx4242"); var stage = protocol.beginTransaction( @@ -751,7 +752,7 @@ private void testDatabaseNameSupport(boolean autoCommitTx) { return expectedStage; }); @SuppressWarnings("unchecked") - var handler = (MessageHandler) mock(MessageHandler.class); + var handler = (MessageHandler) mock(MessageHandler.class); var stage = protocol.beginTransaction( connection, diff --git a/bolt-api-netty/src/test/java/org/neo4j/driver/internal/bolt/basicimpl/impl/messaging/v57/BoltProtocolV57Test.java b/bolt-api-netty/src/test/java/org/neo4j/driver/internal/bolt/basicimpl/impl/messaging/v57/BoltProtocolV57Test.java index eb20667f0e..43fb831225 100644 --- a/bolt-api-netty/src/test/java/org/neo4j/driver/internal/bolt/basicimpl/impl/messaging/v57/BoltProtocolV57Test.java +++ b/bolt-api-netty/src/test/java/org/neo4j/driver/internal/bolt/basicimpl/impl/messaging/v57/BoltProtocolV57Test.java @@ -52,6 +52,7 @@ import org.neo4j.bolt.api.test.values.TestValueFactory; import org.neo4j.driver.internal.bolt.api.AccessMode; import org.neo4j.driver.internal.bolt.api.RoutingContext; +import org.neo4j.driver.internal.bolt.api.summary.BeginSummary; import org.neo4j.driver.internal.bolt.api.summary.RunSummary; import org.neo4j.driver.internal.bolt.api.values.Value; import org.neo4j.driver.internal.bolt.api.values.ValueFactory; @@ -160,7 +161,7 @@ void shouldBeginTransactionWithoutBookmark() { return expectedStage; }); @SuppressWarnings("unchecked") - var handler = (MessageHandler) mock(MessageHandler.class); + var handler = (MessageHandler) mock(MessageHandler.class); var stage = protocol.beginTransaction( connection, @@ -204,7 +205,7 @@ void shouldBeginTransactionWithBookmarks() { return expectedStage; }); @SuppressWarnings("unchecked") - var handler = (MessageHandler) mock(MessageHandler.class); + var handler = (MessageHandler) mock(MessageHandler.class); var bookmarks = Collections.singleton("neo4j:bookmark:v1:tx100"); var stage = protocol.beginTransaction( @@ -249,7 +250,7 @@ void shouldBeginTransactionWithConfig() { return expectedStage; }); @SuppressWarnings("unchecked") - var handler = (MessageHandler) mock(MessageHandler.class); + var handler = (MessageHandler) mock(MessageHandler.class); var stage = protocol.beginTransaction( connection, @@ -293,7 +294,7 @@ void shouldBeginTransactionWithBookmarksAndConfig() { return expectedStage; }); @SuppressWarnings("unchecked") - var handler = (MessageHandler) mock(MessageHandler.class); + var handler = (MessageHandler) mock(MessageHandler.class); var bookmarks = Collections.singleton("neo4j:bookmark:v1:tx4242"); var stage = protocol.beginTransaction( @@ -741,7 +742,7 @@ private void testDatabaseNameSupport(boolean autoCommitTx) { return expectedStage; }); @SuppressWarnings("unchecked") - var handler = (MessageHandler) mock(MessageHandler.class); + var handler = (MessageHandler) mock(MessageHandler.class); var stage = protocol.beginTransaction( connection, diff --git a/bolt-api-netty/src/test/java/org/neo4j/driver/internal/bolt/basicimpl/impl/messaging/v58/BoltProtocolV58Test.java b/bolt-api-netty/src/test/java/org/neo4j/driver/internal/bolt/basicimpl/impl/messaging/v58/BoltProtocolV58Test.java new file mode 100644 index 0000000000..4d4cf75879 --- /dev/null +++ b/bolt-api-netty/src/test/java/org/neo4j/driver/internal/bolt/basicimpl/impl/messaging/v58/BoltProtocolV58Test.java @@ -0,0 +1,783 @@ +/* + * Copyright (c) "Neo4j" + * Neo4j Sweden AB [https://neo4j.com] + * + * 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.bolt.basicimpl.impl.messaging.v58; + +import static java.time.Duration.ofSeconds; +import static java.util.Collections.emptyMap; +import static java.util.Collections.singletonMap; +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertFalse; +import static org.junit.jupiter.api.Assertions.assertInstanceOf; +import static org.junit.jupiter.api.Assertions.assertTrue; +import static org.mockito.ArgumentMatchers.any; +import static org.mockito.ArgumentMatchers.eq; +import static org.mockito.BDDMockito.given; +import static org.mockito.BDDMockito.then; +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.verify; +import static org.mockito.Mockito.when; +import static org.neo4j.driver.internal.bolt.api.DatabaseNameUtil.database; +import static org.neo4j.driver.internal.bolt.api.DatabaseNameUtil.defaultDatabase; +import static org.neo4j.driver.internal.bolt.basicimpl.impl.messaging.request.RunWithMetadataMessage.autoCommitTxRunMessage; +import static org.neo4j.driver.internal.bolt.basicimpl.impl.messaging.request.RunWithMetadataMessage.unmanagedTxRunMessage; + +import io.netty.channel.embedded.EmbeddedChannel; +import java.time.Clock; +import java.time.Duration; +import java.util.Collections; +import java.util.Map; +import java.util.Set; +import java.util.concurrent.CompletableFuture; +import java.util.concurrent.CompletionStage; +import org.junit.jupiter.api.AfterEach; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.params.ParameterizedTest; +import org.junit.jupiter.params.provider.EnumSource; +import org.mockito.stubbing.Answer; +import org.neo4j.bolt.api.test.values.TestValueFactory; +import org.neo4j.driver.internal.bolt.api.AccessMode; +import org.neo4j.driver.internal.bolt.api.RoutingContext; +import org.neo4j.driver.internal.bolt.api.summary.BeginSummary; +import org.neo4j.driver.internal.bolt.api.summary.RunSummary; +import org.neo4j.driver.internal.bolt.api.values.Value; +import org.neo4j.driver.internal.bolt.api.values.ValueFactory; +import org.neo4j.driver.internal.bolt.basicimpl.impl.NoopLoggingProvider; +import org.neo4j.driver.internal.bolt.basicimpl.impl.async.connection.ChannelAttributes; +import org.neo4j.driver.internal.bolt.basicimpl.impl.async.inbound.InboundMessageDispatcher; +import org.neo4j.driver.internal.bolt.basicimpl.impl.handlers.BeginTxResponseHandler; +import org.neo4j.driver.internal.bolt.basicimpl.impl.handlers.CommitTxResponseHandler; +import org.neo4j.driver.internal.bolt.basicimpl.impl.handlers.PullResponseHandlerImpl; +import org.neo4j.driver.internal.bolt.basicimpl.impl.handlers.RollbackTxResponseHandler; +import org.neo4j.driver.internal.bolt.basicimpl.impl.handlers.RunResponseHandler; +import org.neo4j.driver.internal.bolt.basicimpl.impl.messaging.BoltProtocol; +import org.neo4j.driver.internal.bolt.basicimpl.impl.messaging.MessageFormat; +import org.neo4j.driver.internal.bolt.basicimpl.impl.messaging.MessageHandler; +import org.neo4j.driver.internal.bolt.basicimpl.impl.messaging.PullMessageHandler; +import org.neo4j.driver.internal.bolt.basicimpl.impl.messaging.request.BeginMessage; +import org.neo4j.driver.internal.bolt.basicimpl.impl.messaging.request.CommitMessage; +import org.neo4j.driver.internal.bolt.basicimpl.impl.messaging.request.HelloMessage; +import org.neo4j.driver.internal.bolt.basicimpl.impl.messaging.request.LogonMessage; +import org.neo4j.driver.internal.bolt.basicimpl.impl.messaging.request.PullMessage; +import org.neo4j.driver.internal.bolt.basicimpl.impl.messaging.request.RollbackMessage; +import org.neo4j.driver.internal.bolt.basicimpl.impl.messaging.request.TelemetryMessage; +import org.neo4j.driver.internal.bolt.basicimpl.impl.messaging.v57.MessageFormatV57; +import org.neo4j.driver.internal.bolt.basicimpl.impl.spi.Connection; + +public class BoltProtocolV58Test { + private static final ValueFactory valueFactory = TestValueFactory.INSTANCE; + private static final String query = "RETURN $x"; + private static final Map query_params = singletonMap("x", value(42)); + private static final long UNLIMITED_FETCH_SIZE = -1; + private static final Duration txTimeout = ofSeconds(12); + private static final Map txMetadata = singletonMap("x", value(42)); + + protected final BoltProtocol protocol = createProtocol(); + private final EmbeddedChannel channel = new EmbeddedChannel(); + private final InboundMessageDispatcher messageDispatcher = + new InboundMessageDispatcher(channel, NoopLoggingProvider.INSTANCE); + + @SuppressWarnings("SameReturnValue") + protected BoltProtocol createProtocol() { + return BoltProtocolV58.INSTANCE; + } + + @BeforeEach + void beforeEach() { + ChannelAttributes.setMessageDispatcher(channel, messageDispatcher); + } + + @AfterEach + void afterEach() { + channel.finishAndReleaseAll(); + } + + @Test + void shouldCreateMessageFormat() { + assertInstanceOf(expectedMessageFormatType(), protocol.createMessageFormat()); + } + + @Test + void shouldInitializeChannel() { + var clock = mock(Clock.class); + var time = 1L; + when(clock.millis()).thenReturn(time); + + var latestAuthMillisFuture = new CompletableFuture(); + + var future = protocol.initializeChannel( + channel, + "MyDriver/0.0.1", + null, + Collections.emptyMap(), + RoutingContext.EMPTY, + null, + clock, + latestAuthMillisFuture, + valueFactory) + .toCompletableFuture(); + + assertEquals(2, channel.outboundMessages().size()); + assertInstanceOf(HelloMessage.class, channel.outboundMessages().poll()); + assertInstanceOf(LogonMessage.class, channel.outboundMessages().poll()); + assertEquals(2, messageDispatcher.queuedHandlersCount()); + assertFalse(future.isDone()); + + var metadata = Map.of( + "server", value("Neo4j/3.5.0"), + "connection_id", value("bolt-42")); + + messageDispatcher.handleSuccessMessage(metadata); + messageDispatcher.handleSuccessMessage(Collections.emptyMap()); + + assertTrue(future.isDone()); + assertEquals(channel, future.join()); + verify(clock).millis(); + assertTrue(latestAuthMillisFuture.isDone()); + assertEquals(time, latestAuthMillisFuture.join()); + } + + @Test + void shouldBeginTransactionWithoutBookmark() { + var connection = mock(Connection.class); + given(connection.protocol()).willReturn(protocol); + var expectedStage = CompletableFuture.completedStage(null); + given(connection.write(any(), any())).willAnswer((Answer>) invocation -> { + var beginHandler = (BeginTxResponseHandler) invocation.getArgument(1); + beginHandler.onSuccess(emptyMap()); + return expectedStage; + }); + @SuppressWarnings("unchecked") + var handler = (MessageHandler) mock(MessageHandler.class); + + var stage = protocol.beginTransaction( + connection, + defaultDatabase(), + AccessMode.WRITE, + null, + Collections.emptySet(), + null, + Collections.emptyMap(), + null, + null, + handler, + NoopLoggingProvider.INSTANCE, + valueFactory); + + assertEquals(expectedStage, stage); + var message = new BeginMessage( + Collections.emptySet(), + null, + Collections.emptyMap(), + defaultDatabase(), + AccessMode.WRITE, + null, + null, + null, + false, + NoopLoggingProvider.INSTANCE, + valueFactory); + then(connection).should().write(eq(message), any(BeginTxResponseHandler.class)); + then(handler).should().onSummary(any()); + } + + @Test + void shouldBeginTransactionWithBookmarks() { + var connection = mock(Connection.class); + given(connection.protocol()).willReturn(protocol); + var expectedStage = CompletableFuture.completedStage(null); + given(connection.write(any(), any())).willAnswer((Answer>) invocation -> { + var beginHandler = (BeginTxResponseHandler) invocation.getArgument(1); + beginHandler.onSuccess(emptyMap()); + return expectedStage; + }); + @SuppressWarnings("unchecked") + var handler = (MessageHandler) mock(MessageHandler.class); + var bookmarks = Collections.singleton("neo4j:bookmark:v1:tx100"); + + var stage = protocol.beginTransaction( + connection, + defaultDatabase(), + AccessMode.WRITE, + null, + bookmarks, + null, + Collections.emptyMap(), + null, + null, + handler, + NoopLoggingProvider.INSTANCE, + valueFactory); + + assertEquals(expectedStage, stage); + var message = new BeginMessage( + bookmarks, + null, + Collections.emptyMap(), + defaultDatabase(), + AccessMode.WRITE, + null, + null, + null, + false, + NoopLoggingProvider.INSTANCE, + valueFactory); + then(connection).should().write(eq(message), any(BeginTxResponseHandler.class)); + then(handler).should().onSummary(any()); + } + + @Test + void shouldBeginTransactionWithConfig() { + var connection = mock(Connection.class); + given(connection.protocol()).willReturn(protocol); + var expectedStage = CompletableFuture.completedStage(null); + given(connection.write(any(), any())).willAnswer((Answer>) invocation -> { + var beginHandler = (BeginTxResponseHandler) invocation.getArgument(1); + beginHandler.onSuccess(emptyMap()); + return expectedStage; + }); + @SuppressWarnings("unchecked") + var handler = (MessageHandler) mock(MessageHandler.class); + + var stage = protocol.beginTransaction( + connection, + defaultDatabase(), + AccessMode.WRITE, + null, + Collections.emptySet(), + txTimeout, + txMetadata, + null, + null, + handler, + NoopLoggingProvider.INSTANCE, + valueFactory); + + assertEquals(expectedStage, stage); + var message = new BeginMessage( + Collections.emptySet(), + txTimeout, + txMetadata, + defaultDatabase(), + AccessMode.WRITE, + null, + null, + null, + false, + NoopLoggingProvider.INSTANCE, + valueFactory); + then(connection).should().write(eq(message), any(BeginTxResponseHandler.class)); + then(handler).should().onSummary(any()); + } + + @Test + void shouldBeginTransactionWithBookmarksAndConfig() { + var connection = mock(Connection.class); + given(connection.protocol()).willReturn(protocol); + var expectedStage = CompletableFuture.completedStage(null); + given(connection.write(any(), any())).willAnswer((Answer>) invocation -> { + var beginHandler = (BeginTxResponseHandler) invocation.getArgument(1); + beginHandler.onSuccess(emptyMap()); + return expectedStage; + }); + @SuppressWarnings("unchecked") + var handler = (MessageHandler) mock(MessageHandler.class); + var bookmarks = Collections.singleton("neo4j:bookmark:v1:tx4242"); + + var stage = protocol.beginTransaction( + connection, + defaultDatabase(), + AccessMode.WRITE, + null, + bookmarks, + txTimeout, + txMetadata, + null, + null, + handler, + NoopLoggingProvider.INSTANCE, + valueFactory); + + assertEquals(expectedStage, stage); + var message = new BeginMessage( + bookmarks, + txTimeout, + txMetadata, + defaultDatabase(), + AccessMode.WRITE, + null, + null, + null, + false, + NoopLoggingProvider.INSTANCE, + valueFactory); + then(connection).should().write(eq(message), any(BeginTxResponseHandler.class)); + then(handler).should().onSummary(any()); + } + + @Test + void shouldCommitTransaction() { + var bookmarkString = "neo4j:bookmark:v1:tx4242"; + var connection = mock(Connection.class); + given(connection.protocol()).willReturn(protocol); + var expectedStage = CompletableFuture.completedStage(null); + given(connection.write(any(), any())).willAnswer((Answer>) invocation -> { + var commitHandler = (CommitTxResponseHandler) invocation.getArgument(1); + commitHandler.onSuccess(Map.of("bookmark", value(bookmarkString))); + return expectedStage; + }); + @SuppressWarnings("unchecked") + var handler = (MessageHandler) mock(MessageHandler.class); + + var stage = protocol.commitTransaction(connection, handler); + + assertEquals(expectedStage, stage); + then(connection).should().write(eq(CommitMessage.COMMIT), any(CommitTxResponseHandler.class)); + then(handler).should().onSummary(bookmarkString); + } + + @Test + void shouldRollbackTransaction() { + var connection = mock(Connection.class); + given(connection.protocol()).willReturn(protocol); + var expectedStage = CompletableFuture.completedStage(null); + given(connection.write(any(), any())).willAnswer((Answer>) invocation -> { + var rollbackHandler = (RollbackTxResponseHandler) invocation.getArgument(1); + rollbackHandler.onSuccess(Collections.emptyMap()); + return expectedStage; + }); + @SuppressWarnings("unchecked") + var handler = (MessageHandler) mock(MessageHandler.class); + + var stage = protocol.rollbackTransaction(connection, handler); + + assertEquals(expectedStage, stage); + then(connection).should().write(eq(RollbackMessage.ROLLBACK), any(RollbackTxResponseHandler.class)); + then(handler).should().onSummary(any()); + } + + @ParameterizedTest + @EnumSource(AccessMode.class) + void shouldRunInAutoCommitTransactionAndWaitForRunResponse(AccessMode mode) { + testRunAndWaitForRunResponse(true, null, Collections.emptyMap(), mode); + } + + @ParameterizedTest + @EnumSource(AccessMode.class) + void shouldRunInAutoCommitWithConfigTransactionAndWaitForRunResponse(AccessMode mode) { + testRunAndWaitForRunResponse(true, txTimeout, txMetadata, mode); + } + + @ParameterizedTest + @EnumSource(AccessMode.class) + void shouldRunInAutoCommitTransactionAndWaitForSuccessRunResponse(AccessMode mode) { + testSuccessfulRunInAutoCommitTxWithWaitingForResponse( + Collections.emptySet(), null, Collections.emptyMap(), mode); + } + + @ParameterizedTest + @EnumSource(AccessMode.class) + void shouldRunInAutoCommitTransactionWithBookmarkAndConfigAndWaitForSuccessRunResponse(AccessMode mode) { + testSuccessfulRunInAutoCommitTxWithWaitingForResponse( + Collections.singleton("neo4j:bookmark:v1:tx65"), txTimeout, txMetadata, mode); + } + + @ParameterizedTest + @EnumSource(AccessMode.class) + void shouldRunInAutoCommitTransactionAndWaitForFailureRunResponse(AccessMode mode) { + testFailedRunInAutoCommitTxWithWaitingForResponse(Collections.emptySet(), null, Collections.emptyMap(), mode); + } + + @ParameterizedTest + @EnumSource(AccessMode.class) + void shouldRunInAutoCommitTransactionWithBookmarkAndConfigAndWaitForFailureRunResponse(AccessMode mode) { + testFailedRunInAutoCommitTxWithWaitingForResponse( + Collections.singleton("neo4j:bookmark:v1:tx163"), txTimeout, txMetadata, mode); + } + + @ParameterizedTest + @EnumSource(AccessMode.class) + void shouldRunInUnmanagedTransactionAndWaitForRunResponse(AccessMode mode) { + testRunAndWaitForRunResponse(false, null, Collections.emptyMap(), mode); + } + + @Test + void shouldRunInUnmanagedTransactionAndWaitForSuccessRunResponse() { + testRunInUnmanagedTransactionAndWaitForRunResponse(true); + } + + @Test + void shouldRunInUnmanagedTransactionAndWaitForFailureRunResponse() { + testRunInUnmanagedTransactionAndWaitForRunResponse(false); + } + + @Test + void databaseNameInBeginTransaction() { + testDatabaseNameSupport(false); + } + + @Test + void databaseNameForAutoCommitTransactions() { + testDatabaseNameSupport(true); + } + + @Test + void shouldSupportDatabaseNameInBeginTransaction() { + var connection = mock(Connection.class); + var expectedStage = CompletableFuture.completedStage(null); + given(connection.write(any(), any())).willReturn(expectedStage); + var future = protocol.beginTransaction( + connection, + database("foo"), + AccessMode.WRITE, + null, + Collections.emptySet(), + null, + Collections.emptyMap(), + null, + null, + mock(), + NoopLoggingProvider.INSTANCE, + valueFactory); + + assertEquals(expectedStage, future); + then(connection).should().write(any(), any()); + } + + @Test + void shouldNotSupportDatabaseNameForAutoCommitTransactions() { + var connection = mock(Connection.class); + var expectedStage = CompletableFuture.completedStage(null); + given(connection.write(any(), any())).willReturn(expectedStage); + var future = protocol.runAuto( + connection, + database("foo"), + AccessMode.WRITE, + null, + query, + query_params, + Collections.emptySet(), + txTimeout, + txMetadata, + null, + mock(), + NoopLoggingProvider.INSTANCE, + valueFactory); + + assertEquals(expectedStage, future); + then(connection).should().write(any(), any()); + } + + @Test + void shouldTelemetrySendTelemetryMessage() { + var connection = mock(Connection.class); + var expectedStage = CompletableFuture.completedStage(null); + var expectedApi = 1; + given(connection.write(any(), any())).willReturn(expectedStage); + + var future = protocol.telemetry(connection, expectedApi, mock()); + + assertEquals(expectedStage, future); + then(connection).should().write(eq(new TelemetryMessage(expectedApi)), any()); + } + + private Class expectedMessageFormatType() { + return MessageFormatV57.class; + } + + private void testFailedRunInAutoCommitTxWithWaitingForResponse( + Set bookmarks, Duration txTimeout, Map txMetadata, AccessMode mode) { + var connection = mock(Connection.class); + given(connection.protocol()).willReturn(protocol); + var expectedStage = CompletableFuture.completedStage(null); + Throwable error = new RuntimeException(); + given(connection.write(any(), any())).willAnswer((Answer>) invocation -> { + var runHandler = (RunResponseHandler) invocation.getArgument(1); + runHandler.onFailure(error); + return expectedStage; + }); + @SuppressWarnings("unchecked") + var handler = (MessageHandler) mock(MessageHandler.class); + + var stage = protocol.runAuto( + connection, + defaultDatabase(), + mode, + null, + query, + query_params, + bookmarks, + txTimeout, + txMetadata, + null, + handler, + NoopLoggingProvider.INSTANCE, + valueFactory); + assertEquals(expectedStage, stage); + var message = autoCommitTxRunMessage( + query, + query_params, + txTimeout, + txMetadata, + defaultDatabase(), + mode, + bookmarks, + null, + null, + false, + NoopLoggingProvider.INSTANCE, + valueFactory); + then(connection).should().write(eq(message), any(RunResponseHandler.class)); + then(handler).should().onError(error); + } + + private void testSuccessfulRunInAutoCommitTxWithWaitingForResponse( + Set bookmarks, Duration txTimeout, Map txMetadata, AccessMode mode) { + var connection = mock(Connection.class); + given(connection.protocol()).willReturn(protocol); + var expectedRunStage = CompletableFuture.completedStage(null); + var expectedPullStage = CompletableFuture.completedStage(null); + var newBookmarkValue = "neo4j:bookmark:v1:tx98765"; + given(connection.write(any(), any())) + .willAnswer((Answer>) invocation -> { + var runHandler = (RunResponseHandler) invocation.getArgument(1); + runHandler.onSuccess(emptyMap()); + return expectedRunStage; + }) + .willAnswer((Answer>) invocation -> { + var pullHandler = (PullResponseHandlerImpl) invocation.getArgument(1); + pullHandler.onSuccess(Map.of("has_more", value(false), "bookmark", value(newBookmarkValue))); + return expectedPullStage; + }); + @SuppressWarnings("unchecked") + var runHandler = (MessageHandler) mock(MessageHandler.class); + var pullHandler = mock(PullMessageHandler.class); + + var runStage = protocol.runAuto( + connection, + defaultDatabase(), + mode, + null, + query, + query_params, + bookmarks, + txTimeout, + txMetadata, + null, + runHandler, + NoopLoggingProvider.INSTANCE, + valueFactory); + var pullStage = protocol.pull(connection, 0, UNLIMITED_FETCH_SIZE, pullHandler, valueFactory); + + assertEquals(expectedRunStage, runStage); + assertEquals(expectedPullStage, pullStage); + var runMessage = autoCommitTxRunMessage( + query, + query_params, + txTimeout, + txMetadata, + defaultDatabase(), + mode, + bookmarks, + null, + null, + false, + NoopLoggingProvider.INSTANCE, + valueFactory); + then(connection).should().write(eq(runMessage), any(RunResponseHandler.class)); + var pullMessage = new PullMessage(UNLIMITED_FETCH_SIZE, 0L, valueFactory); + then(connection).should().write(eq(pullMessage), any(PullResponseHandlerImpl.class)); + then(runHandler).should().onSummary(any()); + then(pullHandler) + .should() + .onSummary(new PullResponseHandlerImpl.PullSummaryImpl( + false, Map.of("has_more", value(false), "bookmark", value(newBookmarkValue)))); + } + + protected void testRunInUnmanagedTransactionAndWaitForRunResponse(boolean success) { + var connection = mock(Connection.class); + given(connection.protocol()).willReturn(protocol); + var expectedStage = CompletableFuture.completedStage(null); + Throwable error = new RuntimeException(); + given(connection.write(any(), any())).willAnswer((Answer>) invocation -> { + var runHandler = (RunResponseHandler) invocation.getArgument(1); + if (success) { + runHandler.onSuccess(emptyMap()); + } else { + runHandler.onFailure(error); + } + return expectedStage; + }); + @SuppressWarnings("unchecked") + var handler = (MessageHandler) mock(MessageHandler.class); + + var stage = protocol.run(connection, query, query_params, handler); + + assertEquals(expectedStage, stage); + var message = unmanagedTxRunMessage(query, query_params); + then(connection).should().write(eq(message), any(RunResponseHandler.class)); + if (success) { + then(handler).should().onSummary(any()); + } else { + then(handler).should().onError(error); + } + } + + protected void testRunAndWaitForRunResponse( + boolean autoCommitTx, Duration txTimeout, Map txMetadata, AccessMode mode) { + var connection = mock(Connection.class); + given(connection.protocol()).willReturn(protocol); + var expectedStage = CompletableFuture.completedStage(null); + given(connection.write(any(), any())).willAnswer((Answer>) invocation -> { + var runHandler = (RunResponseHandler) invocation.getArgument(1); + runHandler.onSuccess(emptyMap()); + return expectedStage; + }); + @SuppressWarnings("unchecked") + var handler = (MessageHandler) mock(MessageHandler.class); + var initialBookmarks = Collections.singleton("neo4j:bookmark:v1:tx987"); + + if (autoCommitTx) { + var stage = protocol.runAuto( + connection, + defaultDatabase(), + mode, + null, + query, + query_params, + initialBookmarks, + txTimeout, + txMetadata, + null, + handler, + NoopLoggingProvider.INSTANCE, + valueFactory); + assertEquals(expectedStage, stage); + var message = autoCommitTxRunMessage( + query, + query_params, + txTimeout, + txMetadata, + defaultDatabase(), + mode, + initialBookmarks, + null, + null, + false, + NoopLoggingProvider.INSTANCE, + valueFactory); + + then(connection).should().write(eq(message), any(RunResponseHandler.class)); + then(handler).should().onSummary(any()); + } else { + var stage = protocol.run(connection, query, query_params, handler); + + assertEquals(expectedStage, stage); + var message = unmanagedTxRunMessage(query, query_params); + then(connection).should().write(eq(message), any(RunResponseHandler.class)); + then(handler).should().onSummary(any()); + } + } + + private void testDatabaseNameSupport(boolean autoCommitTx) { + var connection = mock(Connection.class); + given(connection.protocol()).willReturn(protocol); + var expectedStage = CompletableFuture.completedStage(null); + if (autoCommitTx) { + given(connection.write(any(), any())).willAnswer((Answer>) invocation -> { + var runHandler = (RunResponseHandler) invocation.getArgument(1); + runHandler.onSuccess(Collections.emptyMap()); + return expectedStage; + }); + @SuppressWarnings("unchecked") + var handler = (MessageHandler) mock(MessageHandler.class); + + var stage = protocol.runAuto( + connection, + defaultDatabase(), + AccessMode.WRITE, + null, + query, + query_params, + Collections.emptySet(), + txTimeout, + txMetadata, + null, + handler, + NoopLoggingProvider.INSTANCE, + valueFactory); + assertEquals(expectedStage, stage); + var message = autoCommitTxRunMessage( + query, + query_params, + txTimeout, + txMetadata, + defaultDatabase(), + AccessMode.WRITE, + Collections.emptySet(), + null, + null, + false, + NoopLoggingProvider.INSTANCE, + valueFactory); + then(connection).should().write(eq(message), any(RunResponseHandler.class)); + then(handler).should().onSummary(any()); + } else { + given(connection.write(any(), any())).willAnswer((Answer>) invocation -> { + var beginHandler = (BeginTxResponseHandler) invocation.getArgument(1); + beginHandler.onSuccess(emptyMap()); + return expectedStage; + }); + @SuppressWarnings("unchecked") + var handler = (MessageHandler) mock(MessageHandler.class); + + var stage = protocol.beginTransaction( + connection, + defaultDatabase(), + AccessMode.WRITE, + null, + Collections.emptySet(), + null, + Collections.emptyMap(), + null, + null, + handler, + NoopLoggingProvider.INSTANCE, + valueFactory); + + assertEquals(expectedStage, stage); + var message = new BeginMessage( + Collections.emptySet(), + null, + Collections.emptyMap(), + defaultDatabase(), + AccessMode.WRITE, + null, + null, + null, + false, + NoopLoggingProvider.INSTANCE, + valueFactory); + then(connection).should().write(eq(message), any(BeginTxResponseHandler.class)); + then(handler).should().onSummary(any()); + } + } + + private static Value value(Object value) { + return valueFactory.value(value); + } +} diff --git a/bolt-api-pooled/src/main/java/org/neo4j/driver/internal/bolt/pooledimpl/PooledBoltConnectionProvider.java b/bolt-api-pooled/src/main/java/org/neo4j/driver/internal/bolt/pooledimpl/PooledBoltConnectionProvider.java index d0b9fc1ef0..a066c6b95f 100644 --- a/bolt-api-pooled/src/main/java/org/neo4j/driver/internal/bolt/pooledimpl/PooledBoltConnectionProvider.java +++ b/bolt-api-pooled/src/main/java/org/neo4j/driver/internal/bolt/pooledimpl/PooledBoltConnectionProvider.java @@ -135,7 +135,8 @@ public CompletionStage connect( String impersonatedUser, BoltProtocolVersion minVersion, NotificationConfig notificationConfig, - Consumer databaseNameConsumer) { + Consumer databaseNameConsumer, + Map additionalParameters) { synchronized (this) { if (closeStage != null) { return CompletableFuture.failedFuture(new IllegalStateException("Connection provider is closed.")); @@ -341,7 +342,8 @@ private void connect( impersonatedUser, minVersion, notificationConfig, - (ignored) -> {}) + (ignored) -> {}, + Collections.emptyMap()) .whenComplete((boltConnection, throwable) -> { var error = FutureUtil.completionExceptionCause(throwable); if (error != null) { @@ -407,6 +409,7 @@ private synchronized ConnectionEntryWithMetadata acquireExistingEntry( var connection = connectionEntry.connection; // unusable if (connection.state() != BoltConnectionState.OPEN) { + connection.close(); iterator.remove(); continue; } @@ -507,7 +510,8 @@ public CompletionStage verifyConnectivity(SecurityPlan securityPlan, Map {}) + (ignored) -> {}, + Collections.emptyMap()) .thenCompose(BoltConnection::close); } @@ -522,7 +526,8 @@ public CompletionStage supportsMultiDb(SecurityPlan securityPlan, Map {}) + (ignored) -> {}, + Collections.emptyMap()) .thenCompose(boltConnection -> { var supports = boltConnection.protocolVersion().compareTo(new BoltProtocolVersion(4, 0)) >= 0; return boltConnection.close().thenApply(ignored -> supports); @@ -540,7 +545,8 @@ public CompletionStage supportsSessionAuth(SecurityPlan securityPlan, M null, null, null, - (ignored) -> {}) + (ignored) -> {}, + Collections.emptyMap()) .thenCompose(boltConnection -> { var supports = new BoltProtocolVersion(5, 1).compareTo(boltConnection.protocolVersion()) <= 0; return boltConnection.close().thenApply(ignored -> supports); diff --git a/bolt-api-pooled/src/main/java/org/neo4j/driver/internal/bolt/pooledimpl/impl/PooledBoltConnection.java b/bolt-api-pooled/src/main/java/org/neo4j/driver/internal/bolt/pooledimpl/impl/PooledBoltConnection.java index 303dba21c9..0980d9a09d 100644 --- a/bolt-api-pooled/src/main/java/org/neo4j/driver/internal/bolt/pooledimpl/impl/PooledBoltConnection.java +++ b/bolt-api-pooled/src/main/java/org/neo4j/driver/internal/bolt/pooledimpl/impl/PooledBoltConnection.java @@ -345,6 +345,11 @@ public boolean telemetrySupported() { return delegate.telemetrySupported(); } + @Override + public boolean serverSideRoutingEnabled() { + return delegate.serverSideRoutingEnabled(); + } + // internal use only public BoltConnection delegate() { return delegate; diff --git a/bolt-api-pooled/src/test/java/org/neo4j/driver/internal/bolt/pooledimpl/PooledBoltConnectionProviderTest.java b/bolt-api-pooled/src/test/java/org/neo4j/driver/internal/bolt/pooledimpl/PooledBoltConnectionProviderTest.java index 96bf4549a4..15a755da18 100644 --- a/bolt-api-pooled/src/test/java/org/neo4j/driver/internal/bolt/pooledimpl/PooledBoltConnectionProviderTest.java +++ b/bolt-api-pooled/src/test/java/org/neo4j/driver/internal/bolt/pooledimpl/PooledBoltConnectionProviderTest.java @@ -138,6 +138,7 @@ void shouldCreateNewConnection() { eq(null), eq(minVersion), eq(notificationConfig), + any(), any())) .willReturn(CompletableFuture.completedStage(connection)); @@ -151,7 +152,8 @@ void shouldCreateNewConnection() { null, minVersion, notificationConfig, - databaseNameConsumer) + databaseNameConsumer, + Collections.emptyMap()) .toCompletableFuture() .join(); @@ -169,6 +171,7 @@ void shouldCreateNewConnection() { eq(null), eq(minVersion), eq(notificationConfig), + any(), any()); assertEquals(1, provider.inUse()); assertEquals(1, provider.size()); @@ -187,6 +190,7 @@ void shouldTimeout() { eq(null), eq(minVersion), eq(notificationConfig), + any(), any())) .willReturn(CompletableFuture.completedStage(connection)); provider = new PooledBoltConnectionProvider( @@ -201,7 +205,8 @@ void shouldTimeout() { null, minVersion, notificationConfig, - databaseNameConsumer) + databaseNameConsumer, + Collections.emptyMap()) .toCompletableFuture() .join(); @@ -215,7 +220,8 @@ void shouldTimeout() { null, minVersion, notificationConfig, - databaseNameConsumer); + databaseNameConsumer, + Collections.emptyMap()); // then var completionException = assertThrows( @@ -243,6 +249,7 @@ void shouldReturnConnectionToPool() { eq(null), eq(minVersion), eq(notificationConfig), + any(), any())) .willReturn(CompletableFuture.completedStage(connection)); var connection = provider.connect( @@ -254,7 +261,8 @@ void shouldReturnConnectionToPool() { null, minVersion, notificationConfig, - databaseNameConsumer) + databaseNameConsumer, + Collections.emptyMap()) .toCompletableFuture() .join(); @@ -291,6 +299,7 @@ void shouldUseExistingConnection() { eq(null), eq(minVersion), eq(notificationConfig), + any(), any())) .willReturn(CompletableFuture.completedStage(connection)); provider.connect( @@ -302,7 +311,8 @@ void shouldUseExistingConnection() { null, minVersion, notificationConfig, - databaseNameConsumer) + databaseNameConsumer, + Collections.emptyMap()) .toCompletableFuture() .join() .close() @@ -320,7 +330,8 @@ void shouldUseExistingConnection() { null, minVersion, notificationConfig, - databaseNameConsumer) + databaseNameConsumer, + Collections.emptyMap()) .toCompletableFuture() .join(); @@ -354,6 +365,7 @@ void shouldClose() { eq(null), eq(minVersion), eq(notificationConfig), + any(), any())) .willReturn(CompletableFuture.completedStage(connection)); provider.connect( @@ -365,7 +377,8 @@ void shouldClose() { null, minVersion, notificationConfig, - databaseNameConsumer) + databaseNameConsumer, + Collections.emptyMap()) .toCompletableFuture() .join() .close() @@ -399,6 +412,7 @@ void shouldVerifyConnectivity() { eq(null), eq(null), eq(null), + any(), any())) .willReturn(CompletableFuture.completedStage(connection)); @@ -419,6 +433,7 @@ void shouldVerifyConnectivity() { eq(null), eq(null), eq(null), + any(), any()); then(connection).should().reset(); then(connection).should().flush(any()); @@ -445,6 +460,7 @@ void shouldSupportMultiDb(BoltProtocolVersion boltProtocolVersion, boolean expec eq(null), eq(null), eq(null), + any(), any())) .willReturn(CompletableFuture.completedStage(connection)); @@ -466,6 +482,7 @@ void shouldSupportMultiDb(BoltProtocolVersion boltProtocolVersion, boolean expec eq(null), eq(null), eq(null), + any(), any()); then(connection).should().reset(); then(connection).should().flush(any()); @@ -498,6 +515,7 @@ void shouldSupportsSessionAuth(BoltProtocolVersion boltProtocolVersion, boolean eq(null), eq(null), eq(null), + any(), any())) .willReturn(CompletableFuture.completedStage(connection)); @@ -519,6 +537,7 @@ void shouldSupportsSessionAuth(BoltProtocolVersion boltProtocolVersion, boolean eq(null), eq(null), eq(null), + any(), any()); then(connection).should().reset(); then(connection).should().flush(any()); @@ -551,6 +570,7 @@ void shouldThrowOnLowerVersion() { eq(null), eq(minVersion), eq(notificationConfig), + any(), any())) .willReturn(CompletableFuture.completedStage(connection)); provider.connect( @@ -562,7 +582,8 @@ void shouldThrowOnLowerVersion() { null, minVersion, notificationConfig, - databaseNameConsumer) + databaseNameConsumer, + Collections.emptyMap()) .toCompletableFuture() .join() .close() @@ -579,7 +600,8 @@ void shouldThrowOnLowerVersion() { null, new BoltProtocolVersion(5, 5), NotificationConfig.defaultConfig(), - databaseNameConsumer) + databaseNameConsumer, + Collections.emptyMap()) .toCompletableFuture(); // then @@ -609,6 +631,7 @@ void shouldTestMaxLifetime() { eq(null), eq(minVersion), eq(notificationConfig), + any(), any())) .willReturn(CompletableFuture.completedStage(connection)) .willReturn(CompletableFuture.completedStage(connection2)); @@ -621,7 +644,8 @@ void shouldTestMaxLifetime() { null, minVersion, notificationConfig, - databaseNameConsumer) + databaseNameConsumer, + Collections.emptyMap()) .toCompletableFuture() .join() .close() @@ -639,7 +663,8 @@ void shouldTestMaxLifetime() { null, minVersion, NotificationConfig.defaultConfig(), - databaseNameConsumer) + databaseNameConsumer, + Collections.emptyMap()) .toCompletableFuture() .join(); @@ -675,6 +700,7 @@ void shouldTestLiveness() { eq(null), eq(minVersion), eq(notificationConfig), + any(), any())) .willReturn(CompletableFuture.completedStage(connection)); provider.connect( @@ -686,7 +712,8 @@ void shouldTestLiveness() { null, minVersion, notificationConfig, - databaseNameConsumer) + databaseNameConsumer, + Collections.emptyMap()) .toCompletableFuture() .join() .close() @@ -704,7 +731,8 @@ void shouldTestLiveness() { null, minVersion, NotificationConfig.defaultConfig(), - databaseNameConsumer) + databaseNameConsumer, + Collections.emptyMap()) .toCompletableFuture() .join(); @@ -745,6 +773,7 @@ void shouldPipelineReauth() { eq(null), eq(minVersion), eq(notificationConfig), + any(), any())) .willReturn(CompletableFuture.completedStage(connection)); provider.connect( @@ -756,7 +785,8 @@ void shouldPipelineReauth() { null, minVersion, notificationConfig, - databaseNameConsumer) + databaseNameConsumer, + Collections.emptyMap()) .toCompletableFuture() .join() .close() @@ -773,7 +803,8 @@ void shouldPipelineReauth() { null, minVersion, NotificationConfig.defaultConfig(), - databaseNameConsumer) + databaseNameConsumer, + Collections.emptyMap()) .toCompletableFuture() .join(); diff --git a/bolt-api-routed/src/main/java/org/neo4j/driver/internal/bolt/routedimpl/RoutedBoltConnectionProvider.java b/bolt-api-routed/src/main/java/org/neo4j/driver/internal/bolt/routedimpl/RoutedBoltConnectionProvider.java index 0c7215a416..af242d3e69 100644 --- a/bolt-api-routed/src/main/java/org/neo4j/driver/internal/bolt/routedimpl/RoutedBoltConnectionProvider.java +++ b/bolt-api-routed/src/main/java/org/neo4j/driver/internal/bolt/routedimpl/RoutedBoltConnectionProvider.java @@ -143,8 +143,11 @@ public CompletionStage connect( String impersonatedUser, BoltProtocolVersion minVersion, NotificationConfig notificationConfig, - Consumer databaseNameConsumer) { + Consumer databaseNameConsumer, + Map additionalParameters) { RoutingTableRegistry registry; + var homeDatabaseHintObj = additionalParameters.get("homeDatabase"); + var homeDatabaseHint = homeDatabaseHintObj instanceof String ? (String) homeDatabaseHintObj : null; synchronized (this) { if (closeFuture != null) { return CompletableFuture.failedFuture(new IllegalStateException("Connection provider is closed.")); @@ -167,7 +170,14 @@ public CompletionStage connect( } }); return registry.ensureRoutingTable( - securityPlan, databaseNameFuture, mode, bookmarks, impersonatedUser, supplier, minVersion) + securityPlan, + databaseNameFuture, + mode, + bookmarks, + impersonatedUser, + supplier, + minVersion, + homeDatabaseHint) .thenApply(routingTableHandler -> { handlerRef.set(routingTableHandler); return routingTableHandler; @@ -213,6 +223,7 @@ public CompletionStage verifyConnectivity(SecurityPlan securityPlan, Map CompletableFuture.completedStage(authMap), + null, null)) .handle((ignored, error) -> { if (error != null) { @@ -301,7 +312,8 @@ private CompletionStage detectFeature( null, null, null, - (ignored) -> {}) + (ignored) -> {}, + Collections.emptyMap()) .thenCompose(boltConnection -> { var featureDetected = featureDetectionFunction.apply(boltConnection); return boltConnection.close().thenApply(ignored -> featureDetected); @@ -386,7 +398,8 @@ private void acquire( impersonatedUser, minVersion, notificationConfig, - (ignored) -> {}) + (ignored) -> {}, + Collections.emptyMap()) .whenComplete((connection, completionError) -> { var error = FutureUtil.completionExceptionCause(completionError); if (error != null) { diff --git a/bolt-api-routed/src/main/java/org/neo4j/driver/internal/bolt/routedimpl/impl/RoutedBoltConnection.java b/bolt-api-routed/src/main/java/org/neo4j/driver/internal/bolt/routedimpl/impl/RoutedBoltConnection.java index 41573504bf..847a3b3337 100644 --- a/bolt-api-routed/src/main/java/org/neo4j/driver/internal/bolt/routedimpl/impl/RoutedBoltConnection.java +++ b/bolt-api-routed/src/main/java/org/neo4j/driver/internal/bolt/routedimpl/impl/RoutedBoltConnection.java @@ -302,6 +302,11 @@ public boolean telemetrySupported() { return delegate.telemetrySupported(); } + @Override + public boolean serverSideRoutingEnabled() { + return delegate.serverSideRoutingEnabled(); + } + private Throwable handledError(Throwable receivedError, boolean notifyHandler) { var error = FutureUtil.completionExceptionCause(receivedError); diff --git a/bolt-api-routed/src/main/java/org/neo4j/driver/internal/bolt/routedimpl/impl/cluster/RediscoveryImpl.java b/bolt-api-routed/src/main/java/org/neo4j/driver/internal/bolt/routedimpl/impl/cluster/RediscoveryImpl.java index 025a5d20e1..e8ef7209c7 100644 --- a/bolt-api-routed/src/main/java/org/neo4j/driver/internal/bolt/routedimpl/impl/cluster/RediscoveryImpl.java +++ b/bolt-api-routed/src/main/java/org/neo4j/driver/internal/bolt/routedimpl/impl/cluster/RediscoveryImpl.java @@ -23,6 +23,7 @@ import java.net.UnknownHostException; import java.util.Collection; +import java.util.Collections; import java.util.HashSet; import java.util.LinkedList; import java.util.List; @@ -368,7 +369,8 @@ private CompletionStage lookupOnRouter( null, minVersion, null, - (ignored) -> {})) + (ignored) -> {}, + Collections.emptyMap())) .thenApply(connection -> { connectionRef.set(connection); return connection; diff --git a/bolt-api-routed/src/main/java/org/neo4j/driver/internal/bolt/routedimpl/impl/cluster/RoutingTableHandler.java b/bolt-api-routed/src/main/java/org/neo4j/driver/internal/bolt/routedimpl/impl/cluster/RoutingTableHandler.java index 3f7131abf5..de3832b7b7 100644 --- a/bolt-api-routed/src/main/java/org/neo4j/driver/internal/bolt/routedimpl/impl/cluster/RoutingTableHandler.java +++ b/bolt-api-routed/src/main/java/org/neo4j/driver/internal/bolt/routedimpl/impl/cluster/RoutingTableHandler.java @@ -43,4 +43,6 @@ CompletionStage ensureRoutingTable( CompletionStage updateRoutingTable(ClusterCompositionLookupResult compositionLookupResult); RoutingTable routingTable(); + + boolean staleRoutingTable(AccessMode mode); } diff --git a/bolt-api-routed/src/main/java/org/neo4j/driver/internal/bolt/routedimpl/impl/cluster/RoutingTableHandlerImpl.java b/bolt-api-routed/src/main/java/org/neo4j/driver/internal/bolt/routedimpl/impl/cluster/RoutingTableHandlerImpl.java index c87e1d6164..487cffae97 100644 --- a/bolt-api-routed/src/main/java/org/neo4j/driver/internal/bolt/routedimpl/impl/cluster/RoutingTableHandlerImpl.java +++ b/bolt-api-routed/src/main/java/org/neo4j/driver/internal/bolt/routedimpl/impl/cluster/RoutingTableHandlerImpl.java @@ -208,4 +208,12 @@ public boolean isRoutingTableAged() { public RoutingTable routingTable() { return routingTable; } + + @Override + public synchronized boolean staleRoutingTable(AccessMode mode) { + if (refreshRoutingTableFuture != null) { + return true; + } + return routingTable.isStaleFor(mode); + } } diff --git a/bolt-api-routed/src/main/java/org/neo4j/driver/internal/bolt/routedimpl/impl/cluster/RoutingTableRegistry.java b/bolt-api-routed/src/main/java/org/neo4j/driver/internal/bolt/routedimpl/impl/cluster/RoutingTableRegistry.java index 24acb6462e..38adaf28d9 100644 --- a/bolt-api-routed/src/main/java/org/neo4j/driver/internal/bolt/routedimpl/impl/cluster/RoutingTableRegistry.java +++ b/bolt-api-routed/src/main/java/org/neo4j/driver/internal/bolt/routedimpl/impl/cluster/RoutingTableRegistry.java @@ -46,7 +46,8 @@ CompletionStage ensureRoutingTable( Set rediscoveryBookmarks, String impersonatedUser, Supplier>> authMapStageSupplier, - BoltProtocolVersion minVersion); + BoltProtocolVersion minVersion, + String homeDatabaseHint); /** * @return all servers in the registry diff --git a/bolt-api-routed/src/main/java/org/neo4j/driver/internal/bolt/routedimpl/impl/cluster/RoutingTableRegistryImpl.java b/bolt-api-routed/src/main/java/org/neo4j/driver/internal/bolt/routedimpl/impl/cluster/RoutingTableRegistryImpl.java index 663b3cef07..7bafffdfaa 100644 --- a/bolt-api-routed/src/main/java/org/neo4j/driver/internal/bolt/routedimpl/impl/cluster/RoutingTableRegistryImpl.java +++ b/bolt-api-routed/src/main/java/org/neo4j/driver/internal/bolt/routedimpl/impl/cluster/RoutingTableRegistryImpl.java @@ -103,7 +103,16 @@ public CompletionStage ensureRoutingTable( Set rediscoveryBookmarks, String impersonatedUser, Supplier>> authMapStageSupplier, - BoltProtocolVersion minVersion) { + BoltProtocolVersion minVersion, + String homeDatabaseHint) { + if (!databaseNameFuture.isDone()) { + if (homeDatabaseHint != null) { + var handler = routingTableHandlers.get(DatabaseNameUtil.database(homeDatabaseHint)); + if (handler != null && !handler.staleRoutingTable(mode)) { + return CompletableFuture.completedFuture(handler); + } + } + } return ensureDatabaseNameIsCompleted( securityPlan, databaseNameFuture, diff --git a/bolt-api-routed/src/test/java/org/neo4j/driver/internal/bolt/routedimpl/impl/cluster/RediscoveryTest.java b/bolt-api-routed/src/test/java/org/neo4j/driver/internal/bolt/routedimpl/impl/cluster/RediscoveryTest.java index f386fa7860..dc5dd38263 100644 --- a/bolt-api-routed/src/test/java/org/neo4j/driver/internal/bolt/routedimpl/impl/cluster/RediscoveryTest.java +++ b/bolt-api-routed/src/test/java/org/neo4j/driver/internal/bolt/routedimpl/impl/cluster/RediscoveryTest.java @@ -676,7 +676,7 @@ void shouldLogScopedIPV6AddressWithStringFormattingLogger() throws UnknownHostEx given(domainNameResolver.resolve(initialRouter.host())).willReturn(new InetAddress[] {address}); var table = routingTableMock(true); var pool = mock(BoltConnectionProvider.class); - given(pool.connect(any(), any(), any(), any(), any(), any(), any(), any(), any())) + given(pool.connect(any(), any(), any(), any(), any(), any(), any(), any(), any(), any())) .willReturn(failedFuture(new BoltServiceUnavailableException("not available"))); var logging = mock(LoggingProvider.class); var logger = mock(System.Logger.class); @@ -722,7 +722,7 @@ private Function connectionProviderGe var boltConnection = setupConnection(entry.getValue()); var boltConnectionProvider = mock(BoltConnectionProvider.class); - given(boltConnectionProvider.connect(any(), any(), any(), any(), any(), any(), any(), any(), any())) + given(boltConnectionProvider.connect(any(), any(), any(), any(), any(), any(), any(), any(), any(), any())) .willReturn(completedFuture(boltConnection)); addressToProvider.put(entry.getKey(), boltConnectionProvider); diff --git a/bolt-api-routed/src/test/java/org/neo4j/driver/internal/bolt/routedimpl/impl/cluster/RoutingTableHandlerTest.java b/bolt-api-routed/src/test/java/org/neo4j/driver/internal/bolt/routedimpl/impl/cluster/RoutingTableHandlerTest.java index d110ece10b..909fb19ea3 100644 --- a/bolt-api-routed/src/test/java/org/neo4j/driver/internal/bolt/routedimpl/impl/cluster/RoutingTableHandlerTest.java +++ b/bolt-api-routed/src/test/java/org/neo4j/driver/internal/bolt/routedimpl/impl/cluster/RoutingTableHandlerTest.java @@ -180,7 +180,8 @@ public CompletionStage ensureRoutingTable( Set rediscoveryBookmarks, String impersonatedUser, Supplier>> authMapStageSupplier, - BoltProtocolVersion minVersion) { + BoltProtocolVersion minVersion, + String homeDatabaseHint) { throw new UnsupportedOperationException(); } @@ -251,7 +252,8 @@ private void testRediscoveryWhenStale(AccessMode mode) { Function connectionProviderGetter = requestedAddress -> { var boltConnectionProvider = mock(BoltConnectionProvider.class); var connection = mock(BoltConnection.class); - given(boltConnectionProvider.connect(any(), any(), any(), any(), any(), any(), any(), any(), any())) + given(boltConnectionProvider.connect( + any(), any(), any(), any(), any(), any(), any(), any(), any(), Collections.emptyMap())) .willReturn(completedFuture(connection)); return boltConnectionProvider; }; @@ -280,7 +282,8 @@ private void testNoRediscoveryWhenNotStale(AccessMode staleMode, AccessMode notS Function connectionProviderGetter = requestedAddress -> { var boltConnectionProvider = mock(BoltConnectionProvider.class); var connection = mock(BoltConnection.class); - given(boltConnectionProvider.connect(any(), any(), any(), any(), any(), any(), any(), any(), any())) + given(boltConnectionProvider.connect( + any(), any(), any(), any(), any(), any(), any(), any(), any(), Collections.emptyMap())) .willReturn(completedFuture(connection)); return boltConnectionProvider; }; @@ -340,14 +343,16 @@ private static Function newConnection return requestedAddress -> { var boltConnectionProvider = mock(BoltConnectionProvider.class); if (unavailableAddresses.contains(requestedAddress)) { - given(boltConnectionProvider.connect(any(), any(), any(), any(), any(), any(), any(), any(), any())) + given(boltConnectionProvider.connect( + any(), any(), any(), any(), any(), any(), any(), any(), any(), Collections.emptyMap())) .willReturn(CompletableFuture.failedFuture( new BoltServiceUnavailableException(requestedAddress + " is unavailable!"))); return boltConnectionProvider; } var connection = mock(BoltConnection.class); when(connection.serverAddress()).thenReturn(requestedAddress); - given(boltConnectionProvider.connect(any(), any(), any(), any(), any(), any(), any(), any(), any())) + given(boltConnectionProvider.connect( + any(), any(), any(), any(), any(), any(), any(), any(), any(), Collections.emptyMap())) .willReturn(completedFuture(connection)); return boltConnectionProvider; }; diff --git a/bolt-api-routed/src/test/java/org/neo4j/driver/internal/bolt/routedimpl/impl/cluster/RoutingTableRegistryImplTest.java b/bolt-api-routed/src/test/java/org/neo4j/driver/internal/bolt/routedimpl/impl/cluster/RoutingTableRegistryImplTest.java index 39cf33b8ca..ca35a7397c 100644 --- a/bolt-api-routed/src/test/java/org/neo4j/driver/internal/bolt/routedimpl/impl/cluster/RoutingTableRegistryImplTest.java +++ b/bolt-api-routed/src/test/java/org/neo4j/driver/internal/bolt/routedimpl/impl/cluster/RoutingTableRegistryImplTest.java @@ -106,7 +106,8 @@ void shouldCreateRoutingTableHandlerIfAbsentWhenFreshRoutingTable(String databas Collections.emptySet(), null, () -> CompletableFuture.completedStage(Collections.emptyMap()), - new BoltProtocolVersion(4, 1)); + new BoltProtocolVersion(4, 1), + null); // Then assertTrue(map.containsKey(database)); @@ -136,7 +137,8 @@ void shouldReturnExistingRoutingTableHandlerWhenFreshRoutingTable(String databas Collections.emptySet(), null, authStageSupplier, - new BoltProtocolVersion(4, 1)) + new BoltProtocolVersion(4, 1), + null) .toCompletableFuture() .join(); @@ -173,7 +175,8 @@ void shouldReturnFreshRoutingTable(AccessMode mode) { Collections.emptySet(), null, authStageSupplier, - new BoltProtocolVersion(4, 1)) + new BoltProtocolVersion(4, 1), + null) .toCompletableFuture() .join(); diff --git a/bolt-api/src/main/java/org/neo4j/driver/internal/bolt/api/BoltConnection.java b/bolt-api/src/main/java/org/neo4j/driver/internal/bolt/api/BoltConnection.java index cf79f50aaf..edf2d6100e 100644 --- a/bolt-api/src/main/java/org/neo4j/driver/internal/bolt/api/BoltConnection.java +++ b/bolt-api/src/main/java/org/neo4j/driver/internal/bolt/api/BoltConnection.java @@ -90,4 +90,6 @@ CompletionStage runInAutoCommitTransaction( BoltProtocolVersion protocolVersion(); boolean telemetrySupported(); + + boolean serverSideRoutingEnabled(); } diff --git a/bolt-api/src/main/java/org/neo4j/driver/internal/bolt/api/BoltConnectionProvider.java b/bolt-api/src/main/java/org/neo4j/driver/internal/bolt/api/BoltConnectionProvider.java index 82fc62c6eb..4ad17afeff 100644 --- a/bolt-api/src/main/java/org/neo4j/driver/internal/bolt/api/BoltConnectionProvider.java +++ b/bolt-api/src/main/java/org/neo4j/driver/internal/bolt/api/BoltConnectionProvider.java @@ -41,7 +41,8 @@ CompletionStage connect( String impersonatedUser, BoltProtocolVersion minVersion, NotificationConfig notificationConfig, - Consumer databaseNameConsumer); + Consumer databaseNameConsumer, + Map additionalParameters); CompletionStage verifyConnectivity(SecurityPlan securityPlan, Map authMap); diff --git a/bolt-api/src/main/java/org/neo4j/driver/internal/bolt/api/summary/BeginSummary.java b/bolt-api/src/main/java/org/neo4j/driver/internal/bolt/api/summary/BeginSummary.java index 2d76c7aa2c..fbeef2f007 100644 --- a/bolt-api/src/main/java/org/neo4j/driver/internal/bolt/api/summary/BeginSummary.java +++ b/bolt-api/src/main/java/org/neo4j/driver/internal/bolt/api/summary/BeginSummary.java @@ -16,4 +16,8 @@ */ package org.neo4j.driver.internal.bolt.api.summary; -public interface BeginSummary {} +import java.util.Optional; + +public interface BeginSummary { + Optional databaseName(); +} diff --git a/bolt-api/src/main/java/org/neo4j/driver/internal/bolt/api/summary/RunSummary.java b/bolt-api/src/main/java/org/neo4j/driver/internal/bolt/api/summary/RunSummary.java index 6bb84f4bfa..3716f9adca 100644 --- a/bolt-api/src/main/java/org/neo4j/driver/internal/bolt/api/summary/RunSummary.java +++ b/bolt-api/src/main/java/org/neo4j/driver/internal/bolt/api/summary/RunSummary.java @@ -17,6 +17,7 @@ package org.neo4j.driver.internal.bolt.api.summary; import java.util.List; +import java.util.Optional; public interface RunSummary { long queryId(); @@ -24,4 +25,6 @@ public interface RunSummary { List keys(); long resultAvailableAfter(); + + Optional databaseName(); } diff --git a/driver/src/main/java/org/neo4j/driver/internal/DriverFactory.java b/driver/src/main/java/org/neo4j/driver/internal/DriverFactory.java index b93b358c4f..ed38f4fe2f 100644 --- a/driver/src/main/java/org/neo4j/driver/internal/DriverFactory.java +++ b/driver/src/main/java/org/neo4j/driver/internal/DriverFactory.java @@ -53,6 +53,8 @@ import org.neo4j.driver.internal.bolt.pooledimpl.PooledBoltConnectionProvider; import org.neo4j.driver.internal.bolt.routedimpl.Rediscovery; import org.neo4j.driver.internal.bolt.routedimpl.RoutedBoltConnectionProvider; +import org.neo4j.driver.internal.boltlistener.BoltConnectionListener; +import org.neo4j.driver.internal.homedb.HomeDatabaseCache; import org.neo4j.driver.internal.metrics.DevNullMetricsProvider; import org.neo4j.driver.internal.metrics.InternalMetricsProvider; import org.neo4j.driver.internal.metrics.MetricsProvider; @@ -164,8 +166,10 @@ private InternalDriver createDriver( Supplier rediscoverySupplier) { DriverBoltConnectionProvider boltConnectionProvider = null; try { - boltConnectionProvider = - createBoltConnectionProvider(uri, config, eventLoopGroup, routingSettings, rediscoverySupplier); + var homeDatabaseCache = + HomeDatabaseCache.newInstance(uri.getScheme().startsWith("neo4j")); + boltConnectionProvider = createBoltConnectionProvider( + uri, config, eventLoopGroup, routingSettings, rediscoverySupplier, homeDatabaseCache); boltConnectionProvider.init( new BoltServerAddress(address.host(), address.port()), new RoutingContext(uri), @@ -174,7 +178,12 @@ private InternalDriver createDriver( config.connectionTimeoutMillis(), metricsProvider.metricsListener()); var sessionFactory = createSessionFactory( - securityPlanManager, boltConnectionProvider, retryLogic, config, authTokenManager); + securityPlanManager, + boltConnectionProvider, + retryLogic, + config, + authTokenManager, + homeDatabaseCache); Supplier> shutdownSupplier = ownsEventLoopGroup ? () -> { var closeFuture = new CompletableFuture(); @@ -213,12 +222,14 @@ private DriverBoltConnectionProvider createBoltConnectionProvider( Config config, EventLoopGroup eventLoopGroup, RoutingSettings routingSettings, - Supplier rediscoverySupplier) { + Supplier rediscoverySupplier, + BoltConnectionListener boltConnectionListener) { DriverBoltConnectionProvider boltConnectionProvider; var clock = createClock(); var loggingProvider = new BoltLoggingProvider(config.logging()); Supplier pooledBoltConnectionProviderSupplier = - () -> createPooledBoltConnectionProvider(config, eventLoopGroup, clock, loggingProvider); + () -> createPooledBoltConnectionProvider( + config, eventLoopGroup, clock, loggingProvider, boltConnectionListener); var errorMapper = ErrorMapper.getInstance(); if (uri.getScheme().startsWith("bolt")) { assertNoRoutingContext(uri, routingSettings); @@ -260,8 +271,14 @@ private BoltConnectionProvider createRoutedBoltConnectionProvider( } private BoltConnectionProvider createPooledBoltConnectionProvider( - Config config, EventLoopGroup eventLoopGroup, Clock clock, LoggingProvider loggingProvider) { + Config config, + EventLoopGroup eventLoopGroup, + Clock clock, + LoggingProvider loggingProvider, + BoltConnectionListener boltConnectionListener) { var nettyBoltConnectionProvider = createNettyBoltConnectionProvider(eventLoopGroup, clock, loggingProvider); + nettyBoltConnectionProvider = BoltConnectionListener.listeningBoltConnectionProvider( + nettyBoltConnectionProvider, boltConnectionListener); return new PooledBoltConnectionProvider( nettyBoltConnectionProvider, config.maxConnectionPoolSize(), @@ -326,8 +343,10 @@ protected SessionFactory createSessionFactory( DriverBoltConnectionProvider connectionProvider, RetryLogic retryLogic, Config config, - AuthTokenManager authTokenManager) { - return new SessionFactoryImpl(securityPlanManager, connectionProvider, retryLogic, config, authTokenManager); + AuthTokenManager authTokenManager, + HomeDatabaseCache homeDatabaseCache) { + return new SessionFactoryImpl( + securityPlanManager, connectionProvider, retryLogic, config, authTokenManager, homeDatabaseCache); } /** 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 18f8c342c7..7f2103da2d 100644 --- a/driver/src/main/java/org/neo4j/driver/internal/SessionFactoryImpl.java +++ b/driver/src/main/java/org/neo4j/driver/internal/SessionFactoryImpl.java @@ -39,6 +39,7 @@ import org.neo4j.driver.internal.bolt.api.DatabaseName; import org.neo4j.driver.internal.bolt.api.DatabaseNameUtil; import org.neo4j.driver.internal.bolt.api.SecurityPlan; +import org.neo4j.driver.internal.homedb.HomeDatabaseCache; import org.neo4j.driver.internal.retry.RetryLogic; import org.neo4j.driver.internal.security.BoltSecurityPlanManager; import org.neo4j.driver.internal.security.InternalAuthToken; @@ -51,13 +52,15 @@ public class SessionFactoryImpl implements SessionFactory { private final boolean leakedSessionsLoggingEnabled; private final long defaultFetchSize; private final AuthTokenManager authTokenManager; + private final HomeDatabaseCache homeDatabaseCache; SessionFactoryImpl( BoltSecurityPlanManager securityPlanManager, DriverBoltConnectionProvider connectionProvider, RetryLogic retryLogic, Config config, - AuthTokenManager authTokenManager) { + AuthTokenManager authTokenManager, + HomeDatabaseCache homeDatabaseCache) { this.securityPlanManager = Objects.requireNonNull(securityPlanManager); this.connectionProvider = connectionProvider; this.leakedSessionsLoggingEnabled = config.logLeakedSessions(); @@ -65,6 +68,7 @@ public class SessionFactoryImpl implements SessionFactory { this.logging = config.logging(); this.defaultFetchSize = config.fetchSize(); this.authTokenManager = authTokenManager; + this.homeDatabaseCache = Objects.requireNonNull(homeDatabaseCache); } @Override @@ -88,7 +92,8 @@ public NetworkSession newInstance( sessionConfig.notificationConfig(), overrideAuthToken, telemetryDisabled, - authTokenManager); + authTokenManager, + homeDatabaseCache); } private Set toDistinctSet(Iterable bookmarks) { @@ -176,7 +181,8 @@ private NetworkSession createSession( NotificationConfig notificationConfig, AuthToken authToken, boolean telemetryDisabled, - AuthTokenManager authTokenManager) { + AuthTokenManager authTokenManager, + HomeDatabaseCache homeDatabaseCache) { Objects.requireNonNull(bookmarks, "bookmarks may not be null"); Objects.requireNonNull(bookmarkManager, "bookmarkManager may not be null"); return leakedSessionsLoggingEnabled @@ -195,7 +201,8 @@ private NetworkSession createSession( notificationConfig, authToken, telemetryDisabled, - authTokenManager) + authTokenManager, + homeDatabaseCache) : new NetworkSession( securityPlanManager, connectionProvider, @@ -211,7 +218,8 @@ private NetworkSession createSession( notificationConfig, authToken, telemetryDisabled, - authTokenManager); + authTokenManager, + homeDatabaseCache); } public DriverBoltConnectionProvider getConnectionProvider() { diff --git a/driver/src/main/java/org/neo4j/driver/internal/adaptedbolt/AdaptingDriverBoltConnection.java b/driver/src/main/java/org/neo4j/driver/internal/adaptedbolt/AdaptingDriverBoltConnection.java index e1e41b97c1..e6ab52fe3d 100644 --- a/driver/src/main/java/org/neo4j/driver/internal/adaptedbolt/AdaptingDriverBoltConnection.java +++ b/driver/src/main/java/org/neo4j/driver/internal/adaptedbolt/AdaptingDriverBoltConnection.java @@ -223,4 +223,9 @@ public BoltProtocolVersion protocolVersion() { public boolean telemetrySupported() { return connection.telemetrySupported(); } + + @Override + public boolean serverSideRoutingEnabled() { + return connection.serverSideRoutingEnabled(); + } } diff --git a/driver/src/main/java/org/neo4j/driver/internal/adaptedbolt/AdaptingDriverBoltConnectionProvider.java b/driver/src/main/java/org/neo4j/driver/internal/adaptedbolt/AdaptingDriverBoltConnectionProvider.java index bf5eab9fee..bd612095a6 100644 --- a/driver/src/main/java/org/neo4j/driver/internal/adaptedbolt/AdaptingDriverBoltConnectionProvider.java +++ b/driver/src/main/java/org/neo4j/driver/internal/adaptedbolt/AdaptingDriverBoltConnectionProvider.java @@ -74,7 +74,8 @@ public CompletionStage connect( String impersonatedUser, BoltProtocolVersion minVersion, NotificationConfig notificationConfig, - Consumer databaseNameConsumer) { + Consumer databaseNameConsumer, + Map additionalParameters) { return delegate.connect( securityPlan, databaseName, @@ -84,7 +85,8 @@ public CompletionStage connect( impersonatedUser, minVersion, notificationConfig, - databaseNameConsumer) + databaseNameConsumer, + additionalParameters) .exceptionally(errorMapper::mapAndTrow) .thenApply(boltConnection -> new AdaptingDriverBoltConnection( boltConnection, diff --git a/driver/src/main/java/org/neo4j/driver/internal/adaptedbolt/DriverBoltConnection.java b/driver/src/main/java/org/neo4j/driver/internal/adaptedbolt/DriverBoltConnection.java index 76d29a4e08..172a3733f5 100644 --- a/driver/src/main/java/org/neo4j/driver/internal/adaptedbolt/DriverBoltConnection.java +++ b/driver/src/main/java/org/neo4j/driver/internal/adaptedbolt/DriverBoltConnection.java @@ -100,4 +100,6 @@ CompletionStage runInAutoCommitTransaction( BoltProtocolVersion protocolVersion(); boolean telemetrySupported(); + + boolean serverSideRoutingEnabled(); } diff --git a/driver/src/main/java/org/neo4j/driver/internal/adaptedbolt/DriverBoltConnectionProvider.java b/driver/src/main/java/org/neo4j/driver/internal/adaptedbolt/DriverBoltConnectionProvider.java index 2a6711d8ae..9ec92c91e9 100644 --- a/driver/src/main/java/org/neo4j/driver/internal/adaptedbolt/DriverBoltConnectionProvider.java +++ b/driver/src/main/java/org/neo4j/driver/internal/adaptedbolt/DriverBoltConnectionProvider.java @@ -51,7 +51,8 @@ CompletionStage connect( String impersonatedUser, BoltProtocolVersion minVersion, NotificationConfig notificationConfig, - Consumer databaseNameConsumer); + Consumer databaseNameConsumer, + Map additionalParameters); CompletionStage verifyConnectivity(SecurityPlan securityPlan, Map authMap); diff --git a/driver/src/main/java/org/neo4j/driver/internal/async/DelegatingBoltConnection.java b/driver/src/main/java/org/neo4j/driver/internal/async/DelegatingBoltConnection.java index 9cb510e2f7..8dac15edb6 100644 --- a/driver/src/main/java/org/neo4j/driver/internal/async/DelegatingBoltConnection.java +++ b/driver/src/main/java/org/neo4j/driver/internal/async/DelegatingBoltConnection.java @@ -194,4 +194,9 @@ public BoltProtocolVersion protocolVersion() { public boolean telemetrySupported() { return delegate.telemetrySupported(); } + + @Override + public boolean serverSideRoutingEnabled() { + return delegate.serverSideRoutingEnabled(); + } } diff --git a/driver/src/main/java/org/neo4j/driver/internal/async/LeakLoggingNetworkSession.java b/driver/src/main/java/org/neo4j/driver/internal/async/LeakLoggingNetworkSession.java index 8ff45c2684..72224fff1c 100644 --- a/driver/src/main/java/org/neo4j/driver/internal/async/LeakLoggingNetworkSession.java +++ b/driver/src/main/java/org/neo4j/driver/internal/async/LeakLoggingNetworkSession.java @@ -30,6 +30,7 @@ import org.neo4j.driver.NotificationConfig; import org.neo4j.driver.internal.adaptedbolt.DriverBoltConnectionProvider; import org.neo4j.driver.internal.bolt.api.DatabaseName; +import org.neo4j.driver.internal.homedb.HomeDatabaseCache; import org.neo4j.driver.internal.retry.RetryLogic; import org.neo4j.driver.internal.security.BoltSecurityPlanManager; import org.neo4j.driver.internal.util.Futures; @@ -52,7 +53,8 @@ public LeakLoggingNetworkSession( NotificationConfig notificationConfig, AuthToken overrideAuthToken, boolean telemetryDisabled, - AuthTokenManager authTokenManager) { + AuthTokenManager authTokenManager, + HomeDatabaseCache homeDatabaseCache) { super( securityPlanManager, connectionProvider, @@ -68,7 +70,8 @@ public LeakLoggingNetworkSession( notificationConfig, overrideAuthToken, telemetryDisabled, - authTokenManager); + authTokenManager, + homeDatabaseCache); this.stackTrace = captureStackTrace(); } diff --git a/driver/src/main/java/org/neo4j/driver/internal/async/NetworkSession.java b/driver/src/main/java/org/neo4j/driver/internal/async/NetworkSession.java index db580425b5..a4d5f1b348 100644 --- a/driver/src/main/java/org/neo4j/driver/internal/async/NetworkSession.java +++ b/driver/src/main/java/org/neo4j/driver/internal/async/NetworkSession.java @@ -21,6 +21,7 @@ import static org.neo4j.driver.internal.util.Futures.completionExceptionCause; import java.util.Collections; +import java.util.HashMap; import java.util.HashSet; import java.util.Map; import java.util.Objects; @@ -30,7 +31,6 @@ import java.util.concurrent.CompletionStage; import java.util.concurrent.TimeoutException; import java.util.concurrent.atomic.AtomicBoolean; -import java.util.concurrent.atomic.AtomicReference; import java.util.function.Consumer; import java.util.function.Function; import java.util.function.Supplier; @@ -63,6 +63,7 @@ import org.neo4j.driver.internal.bolt.api.DatabaseName; import org.neo4j.driver.internal.bolt.api.DatabaseNameUtil; import org.neo4j.driver.internal.bolt.api.NotificationConfig; +import org.neo4j.driver.internal.bolt.api.SecurityPlan; import org.neo4j.driver.internal.bolt.api.TelemetryApi; import org.neo4j.driver.internal.bolt.api.exception.MinVersionAcquisitionException; import org.neo4j.driver.internal.bolt.api.summary.RunSummary; @@ -70,6 +71,8 @@ import org.neo4j.driver.internal.cursor.ResultCursorImpl; import org.neo4j.driver.internal.cursor.RxResultCursor; import org.neo4j.driver.internal.cursor.RxResultCursorImpl; +import org.neo4j.driver.internal.homedb.HomeDatabaseCache; +import org.neo4j.driver.internal.homedb.HomeDatabaseCacheKey; import org.neo4j.driver.internal.logging.PrefixedLogger; import org.neo4j.driver.internal.retry.RetryLogic; import org.neo4j.driver.internal.security.BoltSecurityPlanManager; @@ -78,6 +81,7 @@ import org.neo4j.driver.internal.util.Futures; public class NetworkSession { + private static final String HOME_DATABASE_KEY = "homeDatabase"; private final BoltSecurityPlanManager securityPlanManager; private final DriverBoltConnectionProvider boltConnectionProvider; private final NetworkSessionConnectionContext connectionContext; @@ -99,6 +103,8 @@ public class NetworkSession { private final NotificationConfig notificationConfig; private final boolean telemetryDisabled; private final AuthTokenManager authTokenManager; + private final HomeDatabaseCache homeDatabaseCache; + private final HomeDatabaseCacheKey homeDatabaseKey; public NetworkSession( BoltSecurityPlanManager securityPlanManager, @@ -115,7 +121,8 @@ public NetworkSession( org.neo4j.driver.NotificationConfig notificationConfig, AuthToken overrideAuthToken, boolean telemetryDisabled, - AuthTokenManager authTokenManager) { + AuthTokenManager authTokenManager, + HomeDatabaseCache homeDatabaseCache) { Objects.requireNonNull(bookmarks, "bookmarks may not be null"); Objects.requireNonNull(bookmarkManager, "bookmarkManager may not be null"); this.securityPlanManager = Objects.requireNonNull(securityPlanManager); @@ -137,6 +144,8 @@ public NetworkSession( this.notificationConfig = NotificationConfigMapper.map(notificationConfig); this.telemetryDisabled = telemetryDisabled; this.authTokenManager = authTokenManager; + this.homeDatabaseCache = Objects.requireNonNull(homeDatabaseCache); + this.homeDatabaseKey = HomeDatabaseCacheKey.of(overrideAuthToken, impersonatedUser); } public CompletionStage runAsync(Query query, TransactionConfig config) { @@ -148,11 +157,18 @@ public CompletionStage runAsync(Query query, TransactionConfig con var apiTelemetryWork = new ApiTelemetryWork(TelemetryApi.AUTO_COMMIT_TRANSACTION); apiTelemetryWork.setEnabled(!telemetryDisabled); var resultCursor = new ResultCursorImpl( - connection, query, fetchSize, this::handleNewBookmark, true, null, null); + connection, + query, + fetchSize, + this::handleNewBookmark, + true, + null, + this::handleDatabaseName, + null); var cursorStage = apiTelemetryWork .pipelineTelemetryIfEnabled(connection) .thenCompose(conn -> conn.runInAutoCommitTransaction( - connectionContext.databaseNameFuture.getNow(null), + connectionContext.databaseNameFuture.getNow(DatabaseNameUtil.defaultDatabase()), asBoltAccessMode(mode), connectionContext.impersonatedUser, determineBookmarks(true).stream() @@ -201,12 +217,12 @@ public CompletionStage runRx( var apiTelemetryWork = new ApiTelemetryWork(TelemetryApi.AUTO_COMMIT_TRANSACTION); apiTelemetryWork.setEnabled(!telemetryDisabled); var runFailed = new AtomicBoolean(false); - var responseHandler = - new RunRxResponseHandler(logging, connection, query, this::handleNewBookmark, runFailed); + var responseHandler = new RunRxResponseHandler( + logging, connection, query, this::handleNewBookmark, runFailed, this::handleDatabaseName); var cursorStage = apiTelemetryWork .pipelineTelemetryIfEnabled(connection) .thenCompose(conn -> conn.runInAutoCommitTransaction( - connectionContext.databaseNameFuture.getNow(null), + connectionContext.databaseNameFuture.getNow(DatabaseNameUtil.defaultDatabase()), asBoltAccessMode(mode), connectionContext.impersonatedUser, determineBookmarks(true).stream() @@ -278,13 +294,14 @@ public CompletionStage beginTransactionAsync( .thenCompose(connection -> { var tx = new UnmanagedTransaction( connection, - connectionContext.databaseNameFuture.getNow(null), + connectionContext.databaseNameFuture.getNow(DatabaseNameUtil.defaultDatabase()), asBoltAccessMode(mode), connectionContext.impersonatedUser, this::handleNewBookmark, fetchSize, notificationConfig, apiTelemetryWork, + this::handleDatabaseName, logging); return tx.beginAsync(determineBookmarks(true), config, txType, flush); }); @@ -398,17 +415,124 @@ protected CompletionStage currentConnectionIsOpen() { connection.isOpen()); // and it's still open } - private org.neo4j.driver.internal.bolt.api.AccessMode asBoltAccessMode(AccessMode mode) { - return switch (mode) { - case WRITE -> org.neo4j.driver.internal.bolt.api.AccessMode.WRITE; - case READ -> org.neo4j.driver.internal.bolt.api.AccessMode.READ; - }; + private void handleDatabaseName(String name) { + connectionContext.databaseNameFuture.complete(DatabaseNameUtil.database(name)); + homeDatabaseCache.put(homeDatabaseKey, name); } private CompletionStage acquireConnection(AccessMode mode) { - var currentConnectionStage = connectionStage; + var overrideAuthToken = connectionContext.overrideAuthToken(); + var authTokenManager = overrideAuthToken != null ? NoopAuthTokenManager.INSTANCE : this.authTokenManager; + var newConnectionStage = pulledResultCursorStage(connectionStage) + .thenCompose(ignored -> securityPlanManager.plan()) + .thenCompose(securityPlan -> acquireConnection(securityPlan, mode) + .thenApply(connection -> (DriverBoltConnection) + new BoltConnectionWithAuthTokenManager(connection, authTokenManager)) + .thenApply(BoltConnectionWithCloseTracking::new) + .exceptionally(this::mapAcquisitionError)); + connectionStage = newConnectionStage.exceptionally(error -> null); + return newConnectionStage; + } + + private BoltConnectionWithCloseTracking mapAcquisitionError(Throwable throwable) { + throwable = Futures.completionExceptionCause(throwable); + if (throwable instanceof TimeoutException) { + throw new ClientException( + GqlStatusError.UNKNOWN.getStatus(), + GqlStatusError.UNKNOWN.getStatusDescription(throwable.getMessage()), + "N/A", + throwable.getMessage(), + GqlStatusError.DIAGNOSTIC_RECORD, + throwable); + } + if (throwable instanceof MinVersionAcquisitionException minVersionAcquisitionException) { + if (connectionContext.overrideAuthToken() == null && connectionContext.impersonatedUser() != null) { + var message = + "Detected connection that does not support impersonation, please make sure to have all servers running 4.4 version or above and communicating" + + " over Bolt version 4.4 or above when using impersonation feature"; + throw new ClientException( + GqlStatusError.UNKNOWN.getStatus(), + GqlStatusError.UNKNOWN.getStatusDescription(message), + "N/A", + message, + GqlStatusError.DIAGNOSTIC_RECORD, + null); + } else { + throw new CompletionException(new UnsupportedFeatureException(String.format( + "Detected Bolt %s connection that does not support the auth token override feature, please make sure to have all servers communicating over Bolt 5.1 or above to use the feature", + minVersionAcquisitionException.version()))); + } + } else { + throw new CompletionException(throwable); + } + } + + private CompletionStage acquireConnection(SecurityPlan securityPlan, AccessMode mode) { + var databaseName = connectionContext.databaseNameFuture().getNow(null); + var impersonatedUser = connectionContext.impersonatedUser(); + var minVersion = minBoltVersion(connectionContext); + var overrideAuthToken = connectionContext.overrideAuthToken(); + var tokenStageSupplier = tokenStageSupplier(overrideAuthToken, authTokenManager); + var accessMode = asBoltAccessMode(mode); + var bookmarks = connectionContext.rediscoveryBookmarks().stream() + .map(Bookmark::value) + .collect(Collectors.toSet()); + var additionalParameters = new HashMap(); + if (databaseName == null) { + homeDatabaseCache.get(homeDatabaseKey).ifPresent(name -> additionalParameters.put(HOME_DATABASE_KEY, name)); + } + + Consumer databaseNameConsumer = (name) -> { + if (name != null) { + if (databaseName == null) { + name.databaseName().ifPresent(n -> homeDatabaseCache.put(homeDatabaseKey, n)); + } + } else { + name = DatabaseNameUtil.defaultDatabase(); + } + connectionContext.databaseNameFuture().complete(name); + }; + + return boltConnectionProvider + .connect( + securityPlan, + databaseName, + tokenStageSupplier, + accessMode, + bookmarks, + impersonatedUser, + minVersion, + driverNotificationConfig, + databaseNameConsumer, + additionalParameters) + .thenCompose(boltConnection -> { + if (additionalParameters.containsKey(HOME_DATABASE_KEY) + && !boltConnection.serverSideRoutingEnabled() + && !connectionContext.databaseNameFuture.isDone()) { + // home database was requested with hint, but the returned connection does not have SSR enabled + additionalParameters.remove(HOME_DATABASE_KEY); + return boltConnection + .close() + .thenCompose(ignored -> boltConnectionProvider.connect( + securityPlan, + null, + tokenStageSupplier, + accessMode, + bookmarks, + impersonatedUser, + minVersion, + driverNotificationConfig, + databaseNameConsumer, + additionalParameters)); + } else { + return CompletableFuture.completedStage(boltConnection); + } + }); + } - var newConnectionStage = resultCursorStage + private CompletionStage pulledResultCursorStage( + CompletionStage connectionStage) { + return resultCursorStage .thenCompose(cursor -> { if (cursor == null) { return completedWithNull(); @@ -423,103 +547,13 @@ private CompletionStage acquireConnection(Acces // 2) previous result has been successful and is fully consumed // 3) previous result failed and error has been consumed - // return existing connection, which should've been released back to the pool by now - return currentConnectionStage.exceptionally(ignore -> null); + // the existing connection should've been released back to the pool by now + return connectionStage.handle((ignored, throwable) -> null); } else { // there exists unconsumed error, re-throw it throw new CompletionException(error); } - }) - .thenCompose(ignored -> { - var databaseName = connectionContext.databaseNameFuture.getNow(null); - - Supplier>> tokenStageSupplier; - var minVersion = new AtomicReference(); - if (connectionContext.impersonatedUser() != null) { - minVersion.set(new BoltProtocolVersion(4, 4)); - } - var overrideAuthToken = connectionContext.overrideAuthToken(); - if (overrideAuthToken != null) { - tokenStageSupplier = () -> CompletableFuture.completedStage(connectionContext.authToken) - .thenApply(token -> ((InternalAuthToken) token).toMap()); - minVersion.set(new BoltProtocolVersion(5, 1)); - } else { - tokenStageSupplier = () -> - authTokenManager.getToken().thenApply(token -> ((InternalAuthToken) token).toMap()); - } - return securityPlanManager.plan().thenCompose(securityPlan -> boltConnectionProvider - .connect( - securityPlan, - databaseName, - tokenStageSupplier, - switch (mode) { - case WRITE -> org.neo4j.driver.internal.bolt.api.AccessMode.WRITE; - case READ -> org.neo4j.driver.internal.bolt.api.AccessMode.READ; - }, - connectionContext.rediscoveryBookmarks().stream() - .map(Bookmark::value) - .collect(Collectors.toSet()), - connectionContext.impersonatedUser(), - minVersion.get(), - driverNotificationConfig, - (name) -> connectionContext - .databaseNameFuture() - .complete(name == null ? DatabaseNameUtil.defaultDatabase() : name)) - .thenApply(connection -> (DriverBoltConnection) new BoltConnectionWithAuthTokenManager( - connection, - overrideAuthToken != null - ? new AuthTokenManager() { - @Override - public CompletionStage getToken() { - return null; - } - - @Override - public boolean handleSecurityException( - AuthToken authToken, SecurityException exception) { - return false; - } - } - : authTokenManager)) - .thenApply(BoltConnectionWithCloseTracking::new) - .exceptionally(throwable -> { - throwable = Futures.completionExceptionCause(throwable); - if (throwable instanceof TimeoutException) { - throw new ClientException( - GqlStatusError.UNKNOWN.getStatus(), - GqlStatusError.UNKNOWN.getStatusDescription(throwable.getMessage()), - "N/A", - throwable.getMessage(), - GqlStatusError.DIAGNOSTIC_RECORD, - throwable); - } - if (throwable - instanceof MinVersionAcquisitionException minVersionAcquisitionException) { - if (overrideAuthToken == null && connectionContext.impersonatedUser() != null) { - var message = - "Detected connection that does not support impersonation, please make sure to have all servers running 4.4 version or above and communicating" - + " over Bolt version 4.4 or above when using impersonation feature"; - throw new ClientException( - GqlStatusError.UNKNOWN.getStatus(), - GqlStatusError.UNKNOWN.getStatusDescription(message), - "N/A", - message, - GqlStatusError.DIAGNOSTIC_RECORD, - null); - } else { - throw new CompletionException(new UnsupportedFeatureException(String.format( - "Detected Bolt %s connection that does not support the auth token override feature, please make sure to have all servers communicating over Bolt 5.1 or above to use the feature", - minVersionAcquisitionException.version()))); - } - } else { - throw new CompletionException(throwable); - } - })); }); - - connectionStage = newConnectionStage.exceptionally(error -> null); - - return newConnectionStage; } private CompletionStage closeTransactionAndReleaseConnection() { @@ -602,6 +636,31 @@ private void assertDatabaseNameFutureIsDone() { } } + private static BoltProtocolVersion minBoltVersion(NetworkSessionConnectionContext connectionContext) { + BoltProtocolVersion minBoltVersion = null; + if (connectionContext.overrideAuthToken() != null) { + minBoltVersion = new BoltProtocolVersion(5, 1); + } else if (connectionContext.impersonatedUser() != null) { + minBoltVersion = new BoltProtocolVersion(4, 4); + } + return minBoltVersion; + } + + private static Supplier>> tokenStageSupplier( + AuthToken overrideAuthToken, AuthTokenManager authTokenManager) { + return overrideAuthToken != null + ? () -> CompletableFuture.completedStage(overrideAuthToken) + .thenApply(token -> ((InternalAuthToken) token).toMap()) + : () -> authTokenManager.getToken().thenApply(token -> ((InternalAuthToken) token).toMap()); + } + + private static org.neo4j.driver.internal.bolt.api.AccessMode asBoltAccessMode(AccessMode mode) { + return switch (mode) { + case WRITE -> org.neo4j.driver.internal.bolt.api.AccessMode.WRITE; + case READ -> org.neo4j.driver.internal.bolt.api.AccessMode.READ; + }; + } + /** * The {@link NetworkSessionConnectionContext#mode} can be mutable for a session connection context */ @@ -654,6 +713,7 @@ public static class RunRxResponseHandler implements DriverResponseHandler { private final Query query; private final Consumer bookmarkConsumer; private final AtomicBoolean runFailed; + private final Consumer databaseNameConsumer; private RunSummary runSummary; private Throwable error; private int ignoredCount; @@ -663,12 +723,14 @@ public RunRxResponseHandler( DriverBoltConnection connection, Query query, Consumer bookmarkConsumer, - AtomicBoolean runFailed) { + AtomicBoolean runFailed, + Consumer databaseNameConsumer) { this.logging = logging; this.connection = connection; this.query = query; this.bookmarkConsumer = bookmarkConsumer; this.runFailed = runFailed; + this.databaseNameConsumer = Objects.requireNonNull(databaseNameConsumer); } @Override @@ -687,6 +749,7 @@ public void onError(Throwable throwable) { @Override public void onRunSummary(RunSummary summary) { runSummary = summary; + summary.databaseName().ifPresent(databaseNameConsumer); } @Override @@ -716,4 +779,18 @@ public void onComplete() { } } } + + private static final class NoopAuthTokenManager implements AuthTokenManager { + static final NoopAuthTokenManager INSTANCE = new NoopAuthTokenManager(); + + @Override + public CompletionStage getToken() { + return null; + } + + @Override + public boolean handleSecurityException(AuthToken authToken, SecurityException exception) { + return false; + } + } } diff --git a/driver/src/main/java/org/neo4j/driver/internal/async/UnmanagedTransaction.java b/driver/src/main/java/org/neo4j/driver/internal/async/UnmanagedTransaction.java index 1bdba867a7..d1330ad7fc 100644 --- a/driver/src/main/java/org/neo4j/driver/internal/async/UnmanagedTransaction.java +++ b/driver/src/main/java/org/neo4j/driver/internal/async/UnmanagedTransaction.java @@ -25,6 +25,7 @@ import java.util.Arrays; import java.util.EnumSet; +import java.util.Objects; import java.util.Set; import java.util.concurrent.CompletableFuture; import java.util.concurrent.CompletionStage; @@ -117,6 +118,7 @@ private enum State { private final String impersonatedUser; private final ApiTelemetryWork apiTelemetryWork; + private final Consumer databaseNameConsumer; public UnmanagedTransaction( DriverBoltConnection connection, @@ -127,6 +129,7 @@ public UnmanagedTransaction( long fetchSize, NotificationConfig notificationConfig, ApiTelemetryWork apiTelemetryWork, + Consumer databaseNameConsumer, Logging logging) { this( connection, @@ -138,6 +141,7 @@ public UnmanagedTransaction( new ResultCursorsHolder(), notificationConfig, apiTelemetryWork, + databaseNameConsumer, logging); } @@ -151,6 +155,7 @@ protected UnmanagedTransaction( ResultCursorsHolder resultCursors, NotificationConfig notificationConfig, ApiTelemetryWork apiTelemetryWork, + Consumer databaseNameConsumer, Logging logging) { this.logging = logging; this.connection = new TerminationAwareBoltConnection(logging, connection, this, this::markTerminated); @@ -162,6 +167,7 @@ protected UnmanagedTransaction( this.fetchSize = fetchSize; this.notificationConfig = notificationConfig; this.apiTelemetryWork = apiTelemetryWork; + this.databaseNameConsumer = Objects.requireNonNull(databaseNameConsumer); } // flush = false is only supported for async mode with a single subsequent run @@ -184,7 +190,7 @@ public CompletionStage beginAsync( notificationConfig)) .thenCompose(connection -> { if (flush) { - var responseHandler = new BeginResponseHandler(apiTelemetryWork); + var responseHandler = new BeginResponseHandler(apiTelemetryWork, databaseNameConsumer); connection .flush(responseHandler) .thenCompose(ignored -> responseHandler.summaryFuture) @@ -227,7 +233,7 @@ public CompletionStage runAsync(Query query) { ensureCanRunQueries(); var parameters = query.parameters().asMap(Values::value); var resultCursor = new ResultCursorImpl( - connection, query, fetchSize, (bookmark) -> {}, false, beginFuture, apiTelemetryWork); + connection, query, fetchSize, (bookmark) -> {}, false, beginFuture, ignored -> {}, apiTelemetryWork); var flushStage = connection .run(query.text(), parameters) .thenCompose(ignored -> connection.pull(-1, fetchSize)) @@ -588,12 +594,14 @@ private static TransactionTerminatedException failedTxException(Throwable cause) private static class BeginResponseHandler implements DriverResponseHandler { final CompletableFuture summaryFuture = new CompletableFuture<>(); private final ApiTelemetryWork apiTelemetryWork; + private final Consumer databaseNameConsumer; private Throwable error; private BeginSummary beginSummary; private int ignoredCount; - private BeginResponseHandler(ApiTelemetryWork apiTelemetryWork) { + private BeginResponseHandler(ApiTelemetryWork apiTelemetryWork, Consumer databaseNameConsumer) { this.apiTelemetryWork = apiTelemetryWork; + this.databaseNameConsumer = Objects.requireNonNull(databaseNameConsumer); } @Override @@ -612,6 +620,7 @@ public void onError(Throwable throwable) { @Override public void onBeginSummary(BeginSummary summary) { beginSummary = summary; + summary.databaseName().ifPresent(databaseNameConsumer); } @Override diff --git a/driver/src/main/java/org/neo4j/driver/internal/boltlistener/BoltConnectionListener.java b/driver/src/main/java/org/neo4j/driver/internal/boltlistener/BoltConnectionListener.java new file mode 100644 index 0000000000..06cc4787f8 --- /dev/null +++ b/driver/src/main/java/org/neo4j/driver/internal/boltlistener/BoltConnectionListener.java @@ -0,0 +1,31 @@ +/* + * Copyright (c) "Neo4j" + * Neo4j Sweden AB [https://neo4j.com] + * + * 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.boltlistener; + +import org.neo4j.driver.internal.bolt.api.BoltConnection; +import org.neo4j.driver.internal.bolt.api.BoltConnectionProvider; + +public interface BoltConnectionListener { + void onOpen(BoltConnection boltConnection); + + void onClose(BoltConnection boltConnection); + + static BoltConnectionProvider listeningBoltConnectionProvider( + BoltConnectionProvider provider, BoltConnectionListener boltConnectionListener) { + return new ListeningBoltConnectionProvider(provider, boltConnectionListener); + } +} diff --git a/driver/src/main/java/org/neo4j/driver/internal/boltlistener/ListeningBoltConnection.java b/driver/src/main/java/org/neo4j/driver/internal/boltlistener/ListeningBoltConnection.java new file mode 100644 index 0000000000..cec9f5c218 --- /dev/null +++ b/driver/src/main/java/org/neo4j/driver/internal/boltlistener/ListeningBoltConnection.java @@ -0,0 +1,204 @@ +/* + * Copyright (c) "Neo4j" + * Neo4j Sweden AB [https://neo4j.com] + * + * 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.boltlistener; + +import java.time.Duration; +import java.util.Map; +import java.util.Objects; +import java.util.Set; +import java.util.concurrent.CompletionStage; +import org.neo4j.driver.internal.bolt.api.AccessMode; +import org.neo4j.driver.internal.bolt.api.AuthData; +import org.neo4j.driver.internal.bolt.api.BoltConnection; +import org.neo4j.driver.internal.bolt.api.BoltConnectionState; +import org.neo4j.driver.internal.bolt.api.BoltProtocolVersion; +import org.neo4j.driver.internal.bolt.api.BoltServerAddress; +import org.neo4j.driver.internal.bolt.api.DatabaseName; +import org.neo4j.driver.internal.bolt.api.NotificationConfig; +import org.neo4j.driver.internal.bolt.api.ResponseHandler; +import org.neo4j.driver.internal.bolt.api.TelemetryApi; +import org.neo4j.driver.internal.bolt.api.TransactionType; +import org.neo4j.driver.internal.bolt.api.values.Value; + +final class ListeningBoltConnection implements BoltConnection { + private final BoltConnection delegate; + private final BoltConnectionListener boltConnectionListener; + + public ListeningBoltConnection(BoltConnection delegate, BoltConnectionListener boltConnectionListener) { + this.delegate = Objects.requireNonNull(delegate); + this.boltConnectionListener = Objects.requireNonNull(boltConnectionListener); + } + + @Override + public CompletionStage onLoop() { + return delegate.onLoop().thenApply(ignored -> this); + } + + @Override + public CompletionStage route( + DatabaseName databaseName, String impersonatedUser, Set bookmarks) { + return delegate.route(databaseName, impersonatedUser, bookmarks).thenApply(ignored -> this); + } + + @Override + public CompletionStage beginTransaction( + DatabaseName databaseName, + AccessMode accessMode, + String impersonatedUser, + Set bookmarks, + TransactionType transactionType, + Duration txTimeout, + Map txMetadata, + String txType, + NotificationConfig notificationConfig) { + return delegate.beginTransaction( + databaseName, + accessMode, + impersonatedUser, + bookmarks, + transactionType, + txTimeout, + txMetadata, + txType, + notificationConfig) + .thenApply(ignored -> this); + } + + @Override + public CompletionStage runInAutoCommitTransaction( + DatabaseName databaseName, + AccessMode accessMode, + String impersonatedUser, + Set bookmarks, + String query, + Map parameters, + Duration txTimeout, + Map txMetadata, + NotificationConfig notificationConfig) { + return delegate.runInAutoCommitTransaction( + databaseName, + accessMode, + impersonatedUser, + bookmarks, + query, + parameters, + txTimeout, + txMetadata, + notificationConfig) + .thenApply(ignored -> this); + } + + @Override + public CompletionStage run(String query, Map parameters) { + return delegate.run(query, parameters).thenApply(ignored -> this); + } + + @Override + public CompletionStage pull(long qid, long request) { + return delegate.pull(qid, request).thenApply(ignored -> this); + } + + @Override + public CompletionStage discard(long qid, long number) { + return delegate.discard(qid, number).thenApply(ignored -> this); + } + + @Override + public CompletionStage commit() { + return delegate.commit().thenApply(ignored -> this); + } + + @Override + public CompletionStage rollback() { + return delegate.rollback().thenApply(ignored -> this); + } + + @Override + public CompletionStage reset() { + return delegate.reset().thenApply(ignored -> this); + } + + @Override + public CompletionStage logoff() { + return delegate.logoff().thenApply(ignored -> this); + } + + @Override + public CompletionStage logon(Map authMap) { + return delegate.logon(authMap).thenApply(ignored -> this); + } + + @Override + public CompletionStage telemetry(TelemetryApi telemetryApi) { + return delegate.telemetry(telemetryApi).thenApply(ignored -> this); + } + + @Override + public CompletionStage clear() { + return delegate.clear().thenApply(ignored -> this); + } + + @Override + public CompletionStage flush(ResponseHandler handler) { + return delegate.flush(handler); + } + + @Override + public CompletionStage forceClose(String reason) { + return delegate.forceClose(reason).whenComplete((ignored, throwable) -> boltConnectionListener.onClose(this)); + } + + @Override + public CompletionStage close() { + return delegate.close().whenComplete((ignored, throwable) -> boltConnectionListener.onClose(this)); + } + + @Override + public BoltConnectionState state() { + return delegate.state(); + } + + @Override + public CompletionStage authData() { + return delegate.authData(); + } + + @Override + public String serverAgent() { + return delegate.serverAgent(); + } + + @Override + public BoltServerAddress serverAddress() { + return delegate.serverAddress(); + } + + @Override + public BoltProtocolVersion protocolVersion() { + return delegate.protocolVersion(); + } + + @Override + public boolean telemetrySupported() { + return delegate.telemetrySupported(); + } + + @Override + public boolean serverSideRoutingEnabled() { + return delegate.serverSideRoutingEnabled(); + } +} diff --git a/driver/src/main/java/org/neo4j/driver/internal/boltlistener/ListeningBoltConnectionProvider.java b/driver/src/main/java/org/neo4j/driver/internal/boltlistener/ListeningBoltConnectionProvider.java new file mode 100644 index 0000000000..8cd6e438fc --- /dev/null +++ b/driver/src/main/java/org/neo4j/driver/internal/boltlistener/ListeningBoltConnectionProvider.java @@ -0,0 +1,108 @@ +/* + * Copyright (c) "Neo4j" + * Neo4j Sweden AB [https://neo4j.com] + * + * 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.boltlistener; + +import java.util.Map; +import java.util.Objects; +import java.util.Set; +import java.util.concurrent.CompletionStage; +import java.util.function.Consumer; +import java.util.function.Supplier; +import org.neo4j.driver.internal.bolt.api.AccessMode; +import org.neo4j.driver.internal.bolt.api.BoltAgent; +import org.neo4j.driver.internal.bolt.api.BoltConnection; +import org.neo4j.driver.internal.bolt.api.BoltConnectionProvider; +import org.neo4j.driver.internal.bolt.api.BoltProtocolVersion; +import org.neo4j.driver.internal.bolt.api.BoltServerAddress; +import org.neo4j.driver.internal.bolt.api.DatabaseName; +import org.neo4j.driver.internal.bolt.api.MetricsListener; +import org.neo4j.driver.internal.bolt.api.NotificationConfig; +import org.neo4j.driver.internal.bolt.api.RoutingContext; +import org.neo4j.driver.internal.bolt.api.SecurityPlan; +import org.neo4j.driver.internal.bolt.api.values.Value; + +final class ListeningBoltConnectionProvider implements BoltConnectionProvider { + private final BoltConnectionProvider delegate; + private final BoltConnectionListener boltConnectionListener; + + public ListeningBoltConnectionProvider( + BoltConnectionProvider delegate, BoltConnectionListener boltConnectionListener) { + this.delegate = Objects.requireNonNull(delegate); + this.boltConnectionListener = Objects.requireNonNull(boltConnectionListener); + } + + @Override + public CompletionStage init( + BoltServerAddress address, + RoutingContext routingContext, + BoltAgent boltAgent, + String userAgent, + int connectTimeoutMillis, + MetricsListener metricsListener) { + return delegate.init(address, routingContext, boltAgent, userAgent, connectTimeoutMillis, metricsListener); + } + + @Override + public CompletionStage connect( + SecurityPlan securityPlan, + DatabaseName databaseName, + Supplier>> authMapStageSupplier, + AccessMode mode, + Set bookmarks, + String impersonatedUser, + BoltProtocolVersion minVersion, + NotificationConfig notificationConfig, + Consumer databaseNameConsumer, + Map additionalParameters) { + return delegate.connect( + securityPlan, + databaseName, + authMapStageSupplier, + mode, + bookmarks, + impersonatedUser, + minVersion, + notificationConfig, + databaseNameConsumer, + additionalParameters) + .thenApply(boltConnection -> { + boltConnection = new ListeningBoltConnection(boltConnection, boltConnectionListener); + boltConnectionListener.onOpen(boltConnection); + return boltConnection; + }); + } + + @Override + public CompletionStage verifyConnectivity(SecurityPlan securityPlan, Map authMap) { + return delegate.verifyConnectivity(securityPlan, authMap); + } + + @Override + public CompletionStage supportsMultiDb(SecurityPlan securityPlan, Map authMap) { + return delegate.supportsMultiDb(securityPlan, authMap); + } + + @Override + public CompletionStage supportsSessionAuth(SecurityPlan securityPlan, Map authMap) { + return delegate.supportsSessionAuth(securityPlan, authMap); + } + + @Override + public CompletionStage close() { + return delegate.close(); + } +} diff --git a/driver/src/main/java/org/neo4j/driver/internal/cursor/ResultCursorImpl.java b/driver/src/main/java/org/neo4j/driver/internal/cursor/ResultCursorImpl.java index 8235cff7cb..4ef7785aaa 100644 --- a/driver/src/main/java/org/neo4j/driver/internal/cursor/ResultCursorImpl.java +++ b/driver/src/main/java/org/neo4j/driver/internal/cursor/ResultCursorImpl.java @@ -75,6 +75,7 @@ public class ResultCursorImpl extends AbstractRecordStateResponseHandler private final CompletableFuture beginFuture; private final ApiTelemetryWork apiTelemetryWork; private final CompletableFuture consumedFuture = new CompletableFuture<>(); + private final Consumer databaseNameConsumer; private RunSummary runSummary; private State state; @@ -104,6 +105,7 @@ public ResultCursorImpl( Consumer bookmarkConsumer, boolean closeOnSummary, CompletableFuture beginFuture, + Consumer databaseNameConsumer, ApiTelemetryWork apiTelemetryWork) { this.boltConnection = Objects.requireNonNull(boltConnection); this.legacyNotifications = new BoltProtocolVersion(5, 5).compareTo(boltConnection.protocolVersion()) > 0; @@ -115,6 +117,7 @@ public ResultCursorImpl( this.state = State.STREAMING; this.beginFuture = beginFuture; this.apiTelemetryWork = apiTelemetryWork; + this.databaseNameConsumer = Objects.requireNonNull(databaseNameConsumer); } public CompletionStage resultCursor() { @@ -577,6 +580,7 @@ public void onTelemetrySummary(TelemetrySummary summary) { @Override public void onBeginSummary(BeginSummary summary) { if (beginFuture != null) { + summary.databaseName().ifPresent(databaseNameConsumer); beginFuture.complete(null); } } @@ -586,6 +590,7 @@ public void onRunSummary(RunSummary summary) { synchronized (this) { runSummary = summary; } + summary.databaseName().ifPresent(databaseNameConsumer); resultCursorFuture.complete(this); } diff --git a/driver/src/main/java/org/neo4j/driver/internal/cursor/RxResultCursorImpl.java b/driver/src/main/java/org/neo4j/driver/internal/cursor/RxResultCursorImpl.java index 7870787641..dc9d8066f7 100644 --- a/driver/src/main/java/org/neo4j/driver/internal/cursor/RxResultCursorImpl.java +++ b/driver/src/main/java/org/neo4j/driver/internal/cursor/RxResultCursorImpl.java @@ -78,6 +78,11 @@ public List keys() { public long resultAvailableAfter() { return -1; } + + @Override + public Optional databaseName() { + return Optional.empty(); + } }; private final Logger log; private final DriverBoltConnection boltConnection; diff --git a/driver/src/main/java/org/neo4j/driver/internal/homedb/DriverHomeDatabaseCacheKey.java b/driver/src/main/java/org/neo4j/driver/internal/homedb/DriverHomeDatabaseCacheKey.java new file mode 100644 index 0000000000..cf71b54b91 --- /dev/null +++ b/driver/src/main/java/org/neo4j/driver/internal/homedb/DriverHomeDatabaseCacheKey.java @@ -0,0 +1,21 @@ +/* + * Copyright (c) "Neo4j" + * Neo4j Sweden AB [https://neo4j.com] + * + * 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.homedb; + +final class DriverHomeDatabaseCacheKey implements HomeDatabaseCacheKey { + static final DriverHomeDatabaseCacheKey INSTANCE = new DriverHomeDatabaseCacheKey(); +} diff --git a/driver/src/main/java/org/neo4j/driver/internal/homedb/HomeDatabaseCache.java b/driver/src/main/java/org/neo4j/driver/internal/homedb/HomeDatabaseCache.java new file mode 100644 index 0000000000..458b40fad3 --- /dev/null +++ b/driver/src/main/java/org/neo4j/driver/internal/homedb/HomeDatabaseCache.java @@ -0,0 +1,31 @@ +/* + * Copyright (c) "Neo4j" + * Neo4j Sweden AB [https://neo4j.com] + * + * 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.homedb; + +import java.time.Clock; +import java.util.Optional; +import org.neo4j.driver.internal.boltlistener.BoltConnectionListener; + +public interface HomeDatabaseCache extends BoltConnectionListener { + Optional get(HomeDatabaseCacheKey key); + + void put(HomeDatabaseCacheKey key, String value); + + static HomeDatabaseCache newInstance(boolean routed) { + return routed ? new HomeDatabaseCacheImpl(1000, Clock.systemUTC()) : new NoopHomeDatabaseCache(); + } +} diff --git a/driver/src/main/java/org/neo4j/driver/internal/homedb/HomeDatabaseCacheImpl.java b/driver/src/main/java/org/neo4j/driver/internal/homedb/HomeDatabaseCacheImpl.java new file mode 100644 index 0000000000..831f17dadc --- /dev/null +++ b/driver/src/main/java/org/neo4j/driver/internal/homedb/HomeDatabaseCacheImpl.java @@ -0,0 +1,100 @@ +/* + * Copyright (c) "Neo4j" + * Neo4j Sweden AB [https://neo4j.com] + * + * 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.homedb; + +import java.time.Clock; +import java.util.Comparator; +import java.util.HashMap; +import java.util.HashSet; +import java.util.Map; +import java.util.Objects; +import java.util.Optional; +import java.util.Set; +import org.neo4j.driver.internal.bolt.api.BoltConnection; + +final class HomeDatabaseCacheImpl implements HomeDatabaseCache { + private final Map keyToEntry = new HashMap<>(); + private final Set ssrEnabledBoltConnections = new HashSet<>(); + private final Set ssrDisabledBoltConnections = new HashSet<>(); + private final int sizeLimit; + private final int pruneSize; + private final Clock clock; + private boolean enabled; + + public HomeDatabaseCacheImpl(int sizeLimit, Clock clock) { + this.sizeLimit = sizeLimit; + this.pruneSize = Math.max( + (int) Math.min(sizeLimit, ((1 / Math.log(Integer.MAX_VALUE)) * 0.8) * sizeLimit * Math.log(sizeLimit)), + 1); + this.clock = Objects.requireNonNull(clock); + } + + @Override + public synchronized Optional get(HomeDatabaseCacheKey key) { + return enabled + ? Optional.ofNullable(keyToEntry.computeIfPresent( + key, (ignored, entry) -> new Entry(entry.database(), clock.millis()))) + .map(Entry::database) + : Optional.empty(); + } + + @Override + public synchronized void put(HomeDatabaseCacheKey key, String value) { + prune(); + keyToEntry.put(key, new Entry(value, -1)); + } + + @Override + public synchronized void onOpen(BoltConnection boltConnection) { + if (boltConnection.serverSideRoutingEnabled()) { + ssrEnabledBoltConnections.add(boltConnection); + } else { + ssrDisabledBoltConnections.add(boltConnection); + } + updateEnabled(); + } + + @Override + public synchronized void onClose(BoltConnection boltConnection) { + if (boltConnection.serverSideRoutingEnabled()) { + ssrEnabledBoltConnections.remove(boltConnection); + } else { + ssrDisabledBoltConnections.remove(boltConnection); + } + updateEnabled(); + } + + private synchronized void prune() { + if (keyToEntry.size() >= sizeLimit) { + var pruningList = keyToEntry.values().stream() + .sorted(Comparator.comparing(Entry::lastUsed)) + .limit(pruneSize) + .toList(); + keyToEntry.values().removeAll(pruningList); + } + } + + private synchronized void updateEnabled() { + enabled = !ssrEnabledBoltConnections.isEmpty() && ssrDisabledBoltConnections.isEmpty(); + } + + synchronized int size() { + return keyToEntry.size(); + } + + private record Entry(String database, long lastUsed) {} +} diff --git a/driver/src/main/java/org/neo4j/driver/internal/homedb/HomeDatabaseCacheKey.java b/driver/src/main/java/org/neo4j/driver/internal/homedb/HomeDatabaseCacheKey.java new file mode 100644 index 0000000000..f16ca791fd --- /dev/null +++ b/driver/src/main/java/org/neo4j/driver/internal/homedb/HomeDatabaseCacheKey.java @@ -0,0 +1,43 @@ +/* + * Copyright (c) "Neo4j" + * Neo4j Sweden AB [https://neo4j.com] + * + * 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.homedb; + +import java.util.Collections; +import org.neo4j.driver.AuthToken; +import org.neo4j.driver.internal.security.InternalAuthToken; + +public interface HomeDatabaseCacheKey { + static HomeDatabaseCacheKey of(AuthToken overrideAuthToken, String impersonatedUser) { + HomeDatabaseCacheKey key; + if (impersonatedUser != null) { + key = new MapHomeDatabaseCacheKey( + Collections.singletonMap(InternalAuthToken.PRINCIPAL_KEY, impersonatedUser)); + } else if (overrideAuthToken != null) { + var tokenMap = ((InternalAuthToken) overrideAuthToken).toMap(); + var scheme = tokenMap.get(InternalAuthToken.SCHEME_KEY); + if (scheme.asString().equals("basic")) { + key = new MapHomeDatabaseCacheKey(Collections.singletonMap( + InternalAuthToken.PRINCIPAL_KEY, tokenMap.get(InternalAuthToken.PRINCIPAL_KEY))); + } else { + key = new MapHomeDatabaseCacheKey(tokenMap); + } + } else { + key = DriverHomeDatabaseCacheKey.INSTANCE; + } + return key; + } +} diff --git a/driver/src/main/java/org/neo4j/driver/internal/homedb/MapHomeDatabaseCacheKey.java b/driver/src/main/java/org/neo4j/driver/internal/homedb/MapHomeDatabaseCacheKey.java new file mode 100644 index 0000000000..c5e1bcd9e5 --- /dev/null +++ b/driver/src/main/java/org/neo4j/driver/internal/homedb/MapHomeDatabaseCacheKey.java @@ -0,0 +1,21 @@ +/* + * Copyright (c) "Neo4j" + * Neo4j Sweden AB [https://neo4j.com] + * + * 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.homedb; + +import java.util.Map; + +record MapHomeDatabaseCacheKey(Map map) implements HomeDatabaseCacheKey {} diff --git a/driver/src/main/java/org/neo4j/driver/internal/homedb/NoopHomeDatabaseCache.java b/driver/src/main/java/org/neo4j/driver/internal/homedb/NoopHomeDatabaseCache.java new file mode 100644 index 0000000000..f23f763194 --- /dev/null +++ b/driver/src/main/java/org/neo4j/driver/internal/homedb/NoopHomeDatabaseCache.java @@ -0,0 +1,36 @@ +/* + * Copyright (c) "Neo4j" + * Neo4j Sweden AB [https://neo4j.com] + * + * 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.homedb; + +import java.util.Optional; +import org.neo4j.driver.internal.bolt.api.BoltConnection; + +final class NoopHomeDatabaseCache implements HomeDatabaseCache { + @Override + public Optional get(HomeDatabaseCacheKey key) { + return Optional.empty(); + } + + @Override + public void put(HomeDatabaseCacheKey key, String value) {} + + @Override + public void onOpen(BoltConnection boltConnection) {} + + @Override + public void onClose(BoltConnection boltConnection) {} +} diff --git a/driver/src/test/java/org/neo4j/driver/ParametersTest.java b/driver/src/test/java/org/neo4j/driver/ParametersTest.java index e4eedf1da8..6f5ce744ed 100644 --- a/driver/src/test/java/org/neo4j/driver/ParametersTest.java +++ b/driver/src/test/java/org/neo4j/driver/ParametersTest.java @@ -117,7 +117,8 @@ private Session mockedSession() { Config.defaultConfig().notificationConfig(), null, false, - mock(AuthTokenManager.class)); + mock(AuthTokenManager.class), + mock()); return new InternalSession(session); } } diff --git a/driver/src/test/java/org/neo4j/driver/internal/DriverFactoryTest.java b/driver/src/test/java/org/neo4j/driver/internal/DriverFactoryTest.java index 2cf234a080..6514c5def6 100644 --- a/driver/src/test/java/org/neo4j/driver/internal/DriverFactoryTest.java +++ b/driver/src/test/java/org/neo4j/driver/internal/DriverFactoryTest.java @@ -46,6 +46,7 @@ import org.neo4j.driver.internal.async.LeakLoggingNetworkSession; import org.neo4j.driver.internal.async.NetworkSession; import org.neo4j.driver.internal.bolt.basicimpl.BootstrapFactory; +import org.neo4j.driver.internal.homedb.HomeDatabaseCache; import org.neo4j.driver.internal.metrics.DevNullMetricsProvider; import org.neo4j.driver.internal.metrics.InternalMetricsProvider; import org.neo4j.driver.internal.metrics.MicrometerMetricsProvider; @@ -157,9 +158,10 @@ protected SessionFactory createSessionFactory( DriverBoltConnectionProvider connectionProvider, RetryLogic retryLogic, Config config, - AuthTokenManager authTokenManager) { + AuthTokenManager authTokenManager, + HomeDatabaseCache homeDatabaseCache) { var sessionFactory = super.createSessionFactory( - securityPlanManager, connectionProvider, retryLogic, config, authTokenManager); + securityPlanManager, connectionProvider, retryLogic, config, authTokenManager, homeDatabaseCache); capturedSessionFactory = sessionFactory; return sessionFactory; } @@ -183,7 +185,8 @@ protected SessionFactory createSessionFactory( DriverBoltConnectionProvider connectionProvider, RetryLogic retryLogic, Config config, - AuthTokenManager authTokenManager) { + AuthTokenManager authTokenManager, + HomeDatabaseCache homeDatabaseCache) { return sessionFactory; } } diff --git a/driver/src/test/java/org/neo4j/driver/internal/InternalResultTest.java b/driver/src/test/java/org/neo4j/driver/internal/InternalResultTest.java index ddd902e385..287b2a16bc 100644 --- a/driver/src/test/java/org/neo4j/driver/internal/InternalResultTest.java +++ b/driver/src/test/java/org/neo4j/driver/internal/InternalResultTest.java @@ -345,7 +345,7 @@ private Result createResult(int numberOfRecords) { when(connection.protocolVersion()).thenReturn(new BoltProtocolVersion(4, 3)); when(connection.serverAgent()).thenReturn("Neo4j/4.2.5"); - var resultCursor = new ResultCursorImpl(connection, query, -1, ignored -> {}, false, null, null); + var resultCursor = new ResultCursorImpl(connection, query, -1, ignored -> {}, false, null, ignored -> {}, null); var runSummary = mock(RunSummary.class); given(runSummary.keys()).willReturn(asList("k1", "k2")); resultCursor.onRunSummary(runSummary); 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 fc9cc32ceb..9dd7faa87e 100644 --- a/driver/src/test/java/org/neo4j/driver/internal/InternalTransactionTest.java +++ b/driver/src/test/java/org/neo4j/driver/internal/InternalTransactionTest.java @@ -69,7 +69,7 @@ void setUp() { connection = connectionMock(new BoltProtocolVersion(4, 0)); var connectionProvider = mock(DriverBoltConnectionProvider.class); given(connection.onLoop()).willReturn(CompletableFuture.completedStage(connection)); - given(connectionProvider.connect(any(), any(), any(), any(), any(), any(), any(), any(), any())) + given(connectionProvider.connect(any(), any(), any(), any(), any(), any(), any(), any(), any(), any())) .willReturn(CompletableFuture.completedFuture(connection)); given(connection.beginTransaction(any(), any(), any(), any(), any(), any(), any(), any(), any())) .willReturn(CompletableFuture.completedStage(connection)); diff --git a/driver/src/test/java/org/neo4j/driver/internal/SessionFactoryImplTest.java b/driver/src/test/java/org/neo4j/driver/internal/SessionFactoryImplTest.java index edae8e68af..41002b0454 100644 --- a/driver/src/test/java/org/neo4j/driver/internal/SessionFactoryImplTest.java +++ b/driver/src/test/java/org/neo4j/driver/internal/SessionFactoryImplTest.java @@ -82,6 +82,7 @@ private static SessionFactory newSessionFactory(Config config) { mock(DriverBoltConnectionProvider.class), new FixedRetryLogic(0), config, - mock(AuthTokenManager.class)); + mock(AuthTokenManager.class), + mock()); } } 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 6674c5bd44..44caee6094 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 @@ -100,7 +100,7 @@ void setUp() { given(connection.onLoop()).willReturn(CompletableFuture.completedStage(connection)); given(connection.close()).willReturn(completedFuture(null)); connectionProvider = mock(DriverBoltConnectionProvider.class); - given(connectionProvider.connect(any(), any(), any(), any(), any(), any(), any(), any(), any())) + given(connectionProvider.connect(any(), any(), any(), any(), any(), any(), any(), any(), any(), any())) .willAnswer((Answer>) invocation -> { var database = (DatabaseName) invocation.getArguments()[1]; @SuppressWarnings("unchecked") @@ -311,7 +311,7 @@ private void testTxRollbackWhenThrows(AccessMode transactionMode) { var e = assertThrows(Exception.class, () -> executeTransaction(asyncSession, transactionMode, work)); assertEquals(error, e); - verify(connectionProvider).connect(any(), any(), any(), any(), any(), any(), any(), any(), any()); + verify(connectionProvider).connect(any(), any(), any(), any(), any(), any(), any(), any(), any(), any()); verify(connection).beginTransaction(any(), any(), any(), any(), any(), any(), any(), any(), any()); 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 8ab7b9161e..3447841f1d 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 @@ -75,7 +75,7 @@ void setUp() { connection = connectionMock(new BoltProtocolVersion(4, 0)); given(connection.onLoop()).willReturn(CompletableFuture.completedStage(connection)); var connectionProvider = mock(DriverBoltConnectionProvider.class); - given(connectionProvider.connect(any(), any(), any(), any(), any(), any(), any(), any(), any())) + given(connectionProvider.connect(any(), any(), any(), any(), any(), any(), any(), any(), any(), any())) .willAnswer((Answer>) invocation -> { var database = (DatabaseName) invocation.getArguments()[1]; @SuppressWarnings("unchecked") 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 825a50fb6f..1f6d125462 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 @@ -145,12 +145,13 @@ private static LeakLoggingNetworkSession newSession(Logging logging, DriverBoltC NotificationConfig.defaultConfig(), null, true, - AuthTokenManagers.basic(AuthTokens::none)); + AuthTokenManagers.basic(AuthTokens::none), + mock()); } private static DriverBoltConnectionProvider connectionProviderMock(DriverBoltConnection connection) { var provider = mock(DriverBoltConnectionProvider.class); - when(provider.connect(any(), any(), any(), any(), any(), any(), any(), any(), any())) + when(provider.connect(any(), any(), any(), any(), any(), any(), any(), any(), any(), any())) .thenReturn(CompletableFuture.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 c68b97be59..a99c59b8a2 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 @@ -91,7 +91,7 @@ void setUp() { given(connection.onLoop()).willReturn(CompletableFuture.completedStage(connection)); given(connection.close()).willReturn(completedFuture(null)); connectionProvider = mock(DriverBoltConnectionProvider.class); - given(connectionProvider.connect(any(), any(), any(), any(), any(), any(), any(), any(), any())) + given(connectionProvider.connect(any(), any(), any(), any(), any(), any(), any(), any(), any(), any())) .willAnswer((Answer>) invocation -> { var database = (DatabaseName) invocation.getArguments()[1]; @SuppressWarnings("unchecked") @@ -241,7 +241,7 @@ void acquiresNewConnectionForRun() { run(session, query); - verify(connectionProvider).connect(any(), any(), any(), any(), any(), any(), any(), any(), any()); + verify(connectionProvider).connect(any(), any(), any(), any(), any(), any(), any(), any(), any(), any()); } @Test @@ -259,7 +259,8 @@ void releasesOpenConnectionUsedForRunWhenSessionIsClosed() { void resetDoesNothingWhenNoTransactionAndNoConnection() { await(session.resetAsync()); - verify(connectionProvider, never()).connect(any(), any(), any(), any(), any(), any(), any(), any(), any()); + verify(connectionProvider, never()) + .connect(any(), any(), any(), any(), any(), any(), any(), any(), any(), any()); } @Test @@ -268,7 +269,8 @@ void closeWithoutConnection() { close(session); - verify(connectionProvider, never()).connect(any(), any(), any(), any(), any(), any(), any(), any(), any()); + verify(connectionProvider, never()) + .connect(any(), any(), any(), any(), any(), any(), any(), any(), any(), any()); } @Test @@ -277,7 +279,7 @@ void acquiresNewConnectionForBeginTx() { var tx = beginTransaction(session); assertNotNull(tx); - verify(connectionProvider).connect(any(), any(), any(), any(), any(), any(), any(), any(), any()); + verify(connectionProvider).connect(any(), any(), any(), any(), any(), any(), any(), any(), any(), any()); } @Test @@ -334,7 +336,7 @@ void releasesConnectionWhenTxIsClosed() { handler.onComplete(); })); var tx = beginTransaction(session); - verify(connectionProvider).connect(any(), any(), any(), any(), any(), any(), any(), any(), any()); + verify(connectionProvider).connect(any(), any(), any(), any(), any(), any(), any(), any(), any(), any()); then(connection).should().flush(any()); var query = "RETURN 42"; await(tx.runAsync(new Query(query))); @@ -419,7 +421,8 @@ private void accessModeUsedToAcquireConnections(AccessMode mode) { var session2 = newSession(connectionProvider, mode); beginTransaction(session2); var argument = ArgumentCaptor.forClass(org.neo4j.driver.internal.bolt.api.AccessMode.class); - verify(connectionProvider).connect(any(), any(), any(), argument.capture(), any(), any(), any(), any(), any()); + verify(connectionProvider) + .connect(any(), any(), any(), argument.capture(), any(), any(), any(), any(), any(), any()); assertEquals( switch (mode) { case READ -> org.neo4j.driver.internal.bolt.api.AccessMode.READ; @@ -446,7 +449,7 @@ void shouldHaveEmptyLastBookmarksInitially() { void shouldDoNothingWhenClosingWithoutAcquiredConnection() { var error = new RuntimeException("Hi"); Mockito.reset(connectionProvider); - given(connectionProvider.connect(any(), any(), any(), any(), any(), any(), any(), any(), any())) + given(connectionProvider.connect(any(), any(), any(), any(), any(), any(), any(), any(), any(), any())) .willReturn(failedFuture(error)); var e = assertThrows(Exception.class, () -> run(session, "RETURN 1")); @@ -459,7 +462,7 @@ void shouldDoNothingWhenClosingWithoutAcquiredConnection() { void shouldRunAfterRunFailure() { var error = new RuntimeException("Hi"); Mockito.reset(connectionProvider); - given(connectionProvider.connect(any(), any(), any(), any(), any(), any(), any(), any(), any())) + given(connectionProvider.connect(any(), any(), any(), any(), any(), any(), any(), any(), any(), any())) .willReturn(failedFuture(error)) .willAnswer((Answer>) invocation -> { var databaseName = (DatabaseName) invocation.getArguments()[1]; @@ -479,7 +482,8 @@ void shouldRunAfterRunFailure() { run(session, query); - verify(connectionProvider, times(2)).connect(any(), any(), any(), any(), any(), any(), any(), any(), any()); + verify(connectionProvider, times(2)) + .connect(any(), any(), any(), any(), any(), any(), any(), any(), any(), any()); verifyAutocommitRunAndPull(connection, query); } @@ -494,7 +498,7 @@ void shouldRunAfterBeginTxFailureOnBookmark() { given(connection2.close()).willReturn(CompletableFuture.completedStage(null)); Mockito.reset(connectionProvider); - given(connectionProvider.connect(any(), any(), any(), any(), any(), any(), any(), any(), any())) + given(connectionProvider.connect(any(), any(), any(), any(), any(), any(), any(), any(), any(), any())) .willAnswer((Answer>) invocation -> { var databaseName = (DatabaseName) invocation.getArguments()[1]; @SuppressWarnings("unchecked") @@ -522,7 +526,8 @@ void shouldRunAfterBeginTxFailureOnBookmark() { run(session, query); - verify(connectionProvider, times(2)).connect(any(), any(), any(), any(), any(), any(), any(), any(), any()); + verify(connectionProvider, times(2)) + .connect(any(), any(), any(), any(), any(), any(), any(), any(), any(), any()); then(connection1).should().beginTransaction(any(), any(), any(), any(), any(), any(), any(), any(), any()); verifyAutocommitRunAndPull(connection2, "RETURN 2"); } @@ -544,7 +549,7 @@ void shouldBeginTxAfterBeginTxFailureOnBookmark() { })); Mockito.reset(connectionProvider); - given(connectionProvider.connect(any(), any(), any(), any(), any(), any(), any(), any(), any())) + given(connectionProvider.connect(any(), any(), any(), any(), any(), any(), any(), any(), any(), any())) .willAnswer((Answer>) invocation -> { var databaseName = (DatabaseName) invocation.getArguments()[1]; @SuppressWarnings("unchecked") @@ -570,7 +575,8 @@ void shouldBeginTxAfterBeginTxFailureOnBookmark() { beginTransaction(session); - verify(connectionProvider, times(2)).connect(any(), any(), any(), any(), any(), any(), any(), any(), any()); + verify(connectionProvider, times(2)) + .connect(any(), any(), any(), any(), any(), any(), any(), any(), any(), any()); then(connection1).should().beginTransaction(any(), any(), any(), any(), any(), any(), any(), any(), any()); then(connection2).should().beginTransaction(any(), any(), any(), any(), any(), any(), any(), any(), any()); } @@ -579,7 +585,7 @@ void shouldBeginTxAfterBeginTxFailureOnBookmark() { void shouldBeginTxAfterRunFailureToAcquireConnection() { var error = new RuntimeException("Hi"); Mockito.reset(connectionProvider); - given(connectionProvider.connect(any(), any(), any(), any(), any(), any(), any(), any(), any())) + given(connectionProvider.connect(any(), any(), any(), any(), any(), any(), any(), any(), any(), any())) .willReturn(failedFuture(error)) .willAnswer((Answer>) invocation -> { var databaseName = (DatabaseName) invocation.getArguments()[1]; @@ -596,7 +602,8 @@ void shouldBeginTxAfterRunFailureToAcquireConnection() { beginTransaction(session); - verify(connectionProvider, times(2)).connect(any(), any(), any(), any(), any(), any(), any(), any(), any()); + verify(connectionProvider, times(2)) + .connect(any(), any(), any(), any(), any(), any(), any(), any(), any(), any()); then(connection).should().beginTransaction(any(), any(), any(), any(), any(), any(), any(), any(), any()); } diff --git a/driver/src/test/java/org/neo4j/driver/internal/async/UnmanagedTransactionTest.java b/driver/src/test/java/org/neo4j/driver/internal/async/UnmanagedTransactionTest.java index caff5b522a..779f63c86a 100644 --- a/driver/src/test/java/org/neo4j/driver/internal/async/UnmanagedTransactionTest.java +++ b/driver/src/test/java/org/neo4j/driver/internal/async/UnmanagedTransactionTest.java @@ -260,6 +260,7 @@ void shouldReleaseConnectionWhenBeginFails() { -1, null, apiTelemetryWork, + mock(), Logging.none()); var bookmarks = Collections.singleton(InternalBookmark.parse("SomeBookmark")); @@ -292,6 +293,7 @@ void shouldNotReleaseConnectionWhenBeginSucceeds() { -1, null, apiTelemetryWork, + mock(), Logging.none()); var bookmarks = Collections.singleton(InternalBookmark.parse("SomeBookmark")); @@ -316,6 +318,7 @@ void shouldReleaseConnectionWhenTerminatedAndCommitted() { -1, null, apiTelemetryWork, + mock(), Logging.none()); tx.markTerminated(null); @@ -344,6 +347,7 @@ void shouldNotCreateCircularExceptionWhenTerminationCauseEqualsToCursorFailure() resultCursorsHolder, null, apiTelemetryWork, + mock(), Logging.none()); tx.markTerminated(terminationCause); @@ -370,6 +374,7 @@ void shouldNotCreateCircularExceptionWhenTerminationCauseDifferentFromCursorFail resultCursorsHolder, null, apiTelemetryWork, + mock(), Logging.none()); tx.markTerminated(terminationCause); @@ -397,6 +402,7 @@ void shouldNotCreateCircularExceptionWhenTerminatedWithoutFailure() { -1, null, apiTelemetryWork, + mock(), Logging.none()); tx.markTerminated(terminationCause); @@ -421,6 +427,7 @@ void shouldReleaseConnectionWhenTerminatedAndRolledBack() { -1, null, apiTelemetryWork, + mock(), Logging.none()); tx.markTerminated(null); @@ -449,6 +456,7 @@ void shouldReleaseConnectionWhenClose() { -1, null, apiTelemetryWork, + mock(), Logging.none()); await(tx.closeAsync()); @@ -478,6 +486,7 @@ void shouldReleaseConnectionOnConnectionAuthorizationExpiredExceptionFailure() { -1, null, apiTelemetryWork, + mock(), Logging.none()); var bookmarks = Collections.singleton(InternalBookmark.parse("SomeBookmark")); var txConfig = TransactionConfig.empty(); @@ -510,6 +519,7 @@ void shouldReleaseConnectionOnConnectionReadTimeoutExceptionFailure() { -1, null, apiTelemetryWork, + mock(), Logging.none()); var bookmarks = Collections.singleton(InternalBookmark.parse("SomeBookmark")); var txConfig = TransactionConfig.empty(); @@ -550,6 +560,7 @@ void shouldReturnExistingStageOnSimilarCompletingAction( -1, null, apiTelemetryWork, + mock(), Logging.none()); var initialStage = mapTransactionAction(initialAction, tx).get(); @@ -612,6 +623,7 @@ void shouldReturnFailingStageOnConflictingCompletingAction( -1, null, apiTelemetryWork, + mock(), Logging.none()); var originalActionStage = mapTransactionAction(initialAction, tx).get(); @@ -672,6 +684,7 @@ void shouldReturnCompletedWithNullStageOnClosingInactiveTransactionExceptCommitt -1, null, apiTelemetryWork, + mock(), Logging.none()); var originalActionStage = mapTransactionAction(originalAction, tx).get(); @@ -875,6 +888,7 @@ private static UnmanagedTransaction beginTx(DriverBoltConnection connection, Set -1, null, apiTelemetryWork, + mock(), Logging.none()); return await(tx.beginAsync(initialBookmarks, TransactionConfig.empty(), null, true)); } diff --git a/driver/src/test/java/org/neo4j/driver/internal/cursor/ResultCursorImplTest.java b/driver/src/test/java/org/neo4j/driver/internal/cursor/ResultCursorImplTest.java index 3ab2160eee..16525016a5 100644 --- a/driver/src/test/java/org/neo4j/driver/internal/cursor/ResultCursorImplTest.java +++ b/driver/src/test/java/org/neo4j/driver/internal/cursor/ResultCursorImplTest.java @@ -70,7 +70,8 @@ class ResultCursorImplTest { void beforeEach() { openMocks(this); given(connection.protocolVersion()).willReturn(new BoltProtocolVersion(5, 5)); - cursor = new ResultCursorImpl(connection, query, fetchSize, bookmarkConsumer, closeOnSummary, null, null); + cursor = new ResultCursorImpl( + connection, query, fetchSize, bookmarkConsumer, closeOnSummary, null, ignored -> {}, null); cursor.onRunSummary(runSummary); } diff --git a/driver/src/test/java/org/neo4j/driver/internal/homedb/HomeDatabaseCacheImplTest.java b/driver/src/test/java/org/neo4j/driver/internal/homedb/HomeDatabaseCacheImplTest.java new file mode 100644 index 0000000000..59600b455e --- /dev/null +++ b/driver/src/test/java/org/neo4j/driver/internal/homedb/HomeDatabaseCacheImplTest.java @@ -0,0 +1,61 @@ +/* + * Copyright (c) "Neo4j" + * Neo4j Sweden AB [https://neo4j.com] + * + * 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.homedb; + +import static org.junit.jupiter.api.Assertions.assertTrue; +import static org.mockito.BDDMockito.given; +import static org.mockito.MockitoAnnotations.openMocks; + +import java.time.Clock; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.params.ParameterizedTest; +import org.junit.jupiter.params.provider.ValueSource; +import org.mockito.Mock; +import org.neo4j.driver.internal.bolt.api.BoltConnection; + +class HomeDatabaseCacheImplTest { + HomeDatabaseCacheImpl cache; + + @Mock + BoltConnection boltConnection; + + @Mock + Clock clock; + + @BeforeEach + @SuppressWarnings("resource") + void beforeEach() { + openMocks(this); + } + + @ParameterizedTest + @ValueSource(ints = {1, 10, 100, 1000, 10000}) + void testPruning(int sizeLimit) { + cache = new HomeDatabaseCacheImpl(sizeLimit, clock); + given(boltConnection.serverSideRoutingEnabled()).willReturn(true); + cache.onOpen(boltConnection); + + for (var i = 0L; i < sizeLimit + 1L; i++) { + var key = HomeDatabaseCacheKey.of(null, String.valueOf(i)); + cache.put(key, String.valueOf(i)); + given(clock.millis()).willReturn(i); + cache.get(key); + } + + assertTrue(cache.size() <= sizeLimit); + } +} diff --git a/driver/src/test/java/org/neo4j/driver/testutil/TestUtil.java b/driver/src/test/java/org/neo4j/driver/testutil/TestUtil.java index ee467117f3..7a39eae0d9 100644 --- a/driver/src/test/java/org/neo4j/driver/testutil/TestUtil.java +++ b/driver/src/test/java/org/neo4j/driver/testutil/TestUtil.java @@ -225,7 +225,8 @@ public static NetworkSession newSession( Config.defaultConfig().notificationConfig(), null, telemetryDisabled, - mock(AuthTokenManager.class)); + mock(AuthTokenManager.class), + mock()); } public static void setupConnectionAnswers( diff --git a/testkit-backend/src/main/java/neo4j/org/testkit/backend/messages/requests/GetFeatures.java b/testkit-backend/src/main/java/neo4j/org/testkit/backend/messages/requests/GetFeatures.java index 273d477a72..ed66161e66 100644 --- a/testkit-backend/src/main/java/neo4j/org/testkit/backend/messages/requests/GetFeatures.java +++ b/testkit-backend/src/main/java/neo4j/org/testkit/backend/messages/requests/GetFeatures.java @@ -44,6 +44,7 @@ public class GetFeatures implements TestkitRequest { "Feature:Bolt:5.5", "Feature:Bolt:5.6", "Feature:Bolt:5.7", + "Feature:Bolt:5.8", "AuthorizationExpiredTreatment", "ConfHint:connection.recv_timeout_seconds", "Feature:Auth:Bearer", @@ -73,7 +74,9 @@ public class GetFeatures implements TestkitRequest { "Feature:API:Driver.SupportsSessionAuth", "Feature:API:RetryableExceptions", "Feature:API:SSLClientCertificate", - "Feature:API:Summary:GqlStatusObjects")); + "Feature:API:Summary:GqlStatusObjects", + "Feature:API:Driver:MaxConnectionLifetime", + "Optimization:HomeDatabaseCache")); private static final Set SYNC_FEATURES = new HashSet<>(Arrays.asList( "Feature:Bolt:3.0", diff --git a/testkit-backend/src/main/java/neo4j/org/testkit/backend/messages/requests/NewDriver.java b/testkit-backend/src/main/java/neo4j/org/testkit/backend/messages/requests/NewDriver.java index 49696cb5db..c5c62ef9f4 100644 --- a/testkit-backend/src/main/java/neo4j/org/testkit/backend/messages/requests/NewDriver.java +++ b/testkit-backend/src/main/java/neo4j/org/testkit/backend/messages/requests/NewDriver.java @@ -108,6 +108,8 @@ public TestkitResponse process(TestkitState testkitState) { .map(NotificationClassification::valueOf) .collect(Collectors.toSet())) .ifPresent(configBuilder::withDisabledNotificationClassifications); + Optional.ofNullable(data.maxConnectionLifetimeMs) + .ifPresent(timeout -> configBuilder.withMaxConnectionLifetime(timeout, TimeUnit.MILLISECONDS)); configBuilder.withDriverMetrics(); var clientCertificateManager = Optional.ofNullable(data.getClientCertificateProviderId()) .map(testkitState::getClientCertificateManager) @@ -299,6 +301,7 @@ public static class NewDriverBody { private Boolean telemetryDisabled; private ClientCertificate clientCertificate; private String clientCertificateProviderId; + private Long maxConnectionLifetimeMs; } @RequiredArgsConstructor diff --git a/testkit-backend/src/main/java/neo4j/org/testkit/backend/messages/requests/StartTest.java b/testkit-backend/src/main/java/neo4j/org/testkit/backend/messages/requests/StartTest.java index 186e8e98d0..d0d3b3c8bf 100644 --- a/testkit-backend/src/main/java/neo4j/org/testkit/backend/messages/requests/StartTest.java +++ b/testkit-backend/src/main/java/neo4j/org/testkit/backend/messages/requests/StartTest.java @@ -73,6 +73,8 @@ public class StartTest implements TestkitRequest { COMMON_SKIP_PATTERN_TO_REASON.put( "^.*\\.TestConnectionAcquisitionTimeoutMs\\.test_should_encompass_the_version_handshake_(in_time|time_out)$", skipMessage); + COMMON_SKIP_PATTERN_TO_REASON.put( + "^.*\\.TestHomeDbMixedCluster\\.test_connection_acquisition_timeout_during_fallback$", skipMessage); skipMessage = "This test needs updating to implement expected behaviour"; COMMON_SKIP_PATTERN_TO_REASON.put( "^.*\\.TestAuthenticationSchemes[^.]+\\.test_custom_scheme_empty$", skipMessage);