diff --git a/driver/clirr-ignored-differences.xml b/driver/clirr-ignored-differences.xml index 59b51ea0af..5ae3f844b2 100644 --- a/driver/clirr-ignored-differences.xml +++ b/driver/clirr-ignored-differences.xml @@ -433,4 +433,28 @@ org.neo4j.driver.BookmarkManager queryTaskBookmarkManager() + + org/neo4j/driver/summary/Notification + 7012 + java.util.Optional severityLevel() + + + + org/neo4j/driver/summary/Notification + 7012 + java.util.Optional rawSeverityLevel() + + + + org/neo4j/driver/summary/Notification + 7012 + java.util.Optional category() + + + + org/neo4j/driver/summary/Notification + 7012 + java.util.Optional rawCategory() + + diff --git a/driver/src/main/java/org/neo4j/driver/Config.java b/driver/src/main/java/org/neo4j/driver/Config.java index 11255f7822..7afdadad09 100644 --- a/driver/src/main/java/org/neo4j/driver/Config.java +++ b/driver/src/main/java/org/neo4j/driver/Config.java @@ -32,6 +32,7 @@ import java.util.Objects; import java.util.concurrent.TimeUnit; import java.util.logging.Level; +import org.neo4j.driver.exceptions.UnsupportedFeatureException; import org.neo4j.driver.internal.SecuritySettings; import org.neo4j.driver.internal.async.pool.PoolSettings; import org.neo4j.driver.internal.cluster.RoutingSettings; @@ -143,6 +144,10 @@ public final class Config implements Serializable { * The user_agent configured for this driver. */ private final String userAgent; + /** + * The notification config. + */ + private final NotificationConfig notificationConfig; /** * The {@link MetricsAdapter}. */ @@ -166,6 +171,7 @@ private Config(ConfigBuilder builder) { this.maxTransactionRetryTimeMillis = builder.maxTransactionRetryTimeMillis; this.resolver = builder.resolver; this.fetchSize = builder.fetchSize; + this.notificationConfig = builder.notificationConfig; this.eventLoopThreads = builder.eventLoopThreads; this.metricsAdapter = builder.metricsAdapter; @@ -311,6 +317,15 @@ public long fetchSize() { return fetchSize; } + /** + * Returns notification config. + * @return the notification config + * @since 5.7 + */ + public NotificationConfig notificationConfig() { + return notificationConfig; + } + /** * Returns the number of {@link io.netty.channel.EventLoop} threads. * @return the number of threads @@ -363,6 +378,7 @@ public static final class ConfigBuilder { private MetricsAdapter metricsAdapter = MetricsAdapter.DEV_NULL; private long fetchSize = FetchSizeUtil.DEFAULT_FETCH_SIZE; private int eventLoopThreads = 0; + private NotificationConfig notificationConfig = NotificationConfig.defaultConfig(); private ConfigBuilder() {} @@ -757,6 +773,22 @@ public ConfigBuilder withUserAgent(String userAgent) { return this; } + /** + * Sets notification config. + *

+ * Any configuration other than the {@link NotificationConfig#defaultConfig()} requires a minimum Bolt protocol + * version 5.2. Otherwise, an {@link UnsupportedFeatureException} will be emitted when the driver comes across a + * Bolt connection that does not support this feature. For instance, when running a query. + * + * @param notificationConfig the notification config + * @return this builder + * @since 5.7 + */ + public ConfigBuilder withNotificationConfig(NotificationConfig notificationConfig) { + this.notificationConfig = Objects.requireNonNull(notificationConfig, "notificationConfig must not be null"); + return this; + } + /** * Extracts the driver version from the driver jar MANIFEST.MF file. */ diff --git a/driver/src/main/java/org/neo4j/driver/NotificationCategory.java b/driver/src/main/java/org/neo4j/driver/NotificationCategory.java new file mode 100644 index 0000000000..6c33736366 --- /dev/null +++ b/driver/src/main/java/org/neo4j/driver/NotificationCategory.java @@ -0,0 +1,73 @@ +/* + * Copyright (c) "Neo4j" + * Neo4j Sweden AB [http://neo4j.com] + * + * This file is part of Neo4j. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.neo4j.driver; + +import java.io.Serializable; +import org.neo4j.driver.internal.InternalNotificationCategory; +import org.neo4j.driver.internal.InternalNotificationCategory.Type; + +/** + * Notification category. + * + * @since 5.7 + */ +public sealed interface NotificationCategory extends Serializable permits InternalNotificationCategory { + /** + * A hint category. + *

+ * For instance, the given hint cannot be satisfied. + */ + NotificationCategory HINT = new InternalNotificationCategory(Type.HINT); + + /** + * An unrecognized category. + *

+ * For instance, the query or command mentions entities that are unknown to the system. + */ + NotificationCategory UNRECOGNIZED = new InternalNotificationCategory(Type.UNRECOGNIZED); + + /** + * An unsupported category. + *

+ * For instance, the query/command is trying to use features that are not supported by the current system or using + * features that are experimental and should not be used in production. + */ + NotificationCategory UNSUPPORTED = new InternalNotificationCategory(Type.UNSUPPORTED); + + /** + * A performance category. + *

+ * For instance, the query uses costly operations and might be slow. + */ + NotificationCategory PERFORMANCE = new InternalNotificationCategory(Type.PERFORMANCE); + + /** + * A deprecation category. + *

+ * For instance, the query/command use deprecated features that should be replaced. + */ + NotificationCategory DEPRECATION = new InternalNotificationCategory(Type.DEPRECATION); + + /** + * A generic category. + *

+ * For instance, notifications that are not part of a more specific class. + */ + NotificationCategory GENERIC = new InternalNotificationCategory(Type.GENERIC); +} diff --git a/driver/src/main/java/org/neo4j/driver/NotificationConfig.java b/driver/src/main/java/org/neo4j/driver/NotificationConfig.java new file mode 100644 index 0000000000..655474fcdd --- /dev/null +++ b/driver/src/main/java/org/neo4j/driver/NotificationConfig.java @@ -0,0 +1,81 @@ +/* + * Copyright (c) "Neo4j" + * Neo4j Sweden AB [http://neo4j.com] + * + * This file is part of Neo4j. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.neo4j.driver; + +import java.io.Serializable; +import java.util.Set; +import org.neo4j.driver.internal.InternalNotificationConfig; +import org.neo4j.driver.internal.InternalNotificationSeverity; +import org.neo4j.driver.summary.ResultSummary; + +/** + * A notification configuration defining what notifications should be supplied by the server. + *

+ * There are currently 2 optional settings that may be activated: + *

+ *

+ * By default, both options are not activated. + * + * @since 5.7 + * @see ResultSummary#notifications() + * @see org.neo4j.driver.summary.Notification + */ +public sealed interface NotificationConfig extends Serializable permits InternalNotificationConfig { + /** + * Returns a default notification configuration. + *

+ * The default configuration has no settings activated, meaning the resulting behaviour depends on an upstream + * context. For instance, when this config is set on the session level, the resulting behaviour depends on the + * driver's config. Likewise, when this config is set on the driver level, the resulting behaviour depends on the + * server. + * + * @return the default config + */ + static NotificationConfig defaultConfig() { + return new InternalNotificationConfig(null, null); + } + + /** + * A config that disables all notifications. + * + * @return the config that disables all notifications + */ + static NotificationConfig disableAllConfig() { + return new InternalNotificationConfig(InternalNotificationSeverity.OFF, null); + } + + /** + * Returns a config that sets a minimum severity level for notifications. + * + * @param minimumSeverity the minimum severity level + * @return the config + */ + NotificationConfig enableMinimumSeverity(NotificationSeverity minimumSeverity); + + /** + * Returns a config that disables a set of notification categories. + * + * @param disabledCategories the categories to disable, an empty set means no categories are disabled + * @return the config + */ + NotificationConfig disableCategories(Set disabledCategories); +} diff --git a/driver/src/main/java/org/neo4j/driver/NotificationSeverity.java b/driver/src/main/java/org/neo4j/driver/NotificationSeverity.java new file mode 100644 index 0000000000..e911a18eda --- /dev/null +++ b/driver/src/main/java/org/neo4j/driver/NotificationSeverity.java @@ -0,0 +1,41 @@ +/* + * Copyright (c) "Neo4j" + * Neo4j Sweden AB [http://neo4j.com] + * + * This file is part of Neo4j. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.neo4j.driver; + +import static org.neo4j.driver.internal.InternalNotificationSeverity.Type; + +import java.io.Serializable; +import org.neo4j.driver.internal.InternalNotificationSeverity; + +/** + * Notification severity level. + * + * @since 5.7 + */ +public sealed interface NotificationSeverity extends Serializable, Comparable + permits InternalNotificationSeverity { + /** + * An information severity level. + */ + NotificationSeverity INFORMATION = new InternalNotificationSeverity(Type.INFORMATION, 800); + /** + * A warning severity level. + */ + NotificationSeverity WARNING = new InternalNotificationSeverity(Type.WARNING, 900); +} diff --git a/driver/src/main/java/org/neo4j/driver/SessionConfig.java b/driver/src/main/java/org/neo4j/driver/SessionConfig.java index abcca9f363..aad02a68a4 100644 --- a/driver/src/main/java/org/neo4j/driver/SessionConfig.java +++ b/driver/src/main/java/org/neo4j/driver/SessionConfig.java @@ -27,6 +27,7 @@ import java.util.Objects; import java.util.Optional; import org.neo4j.driver.async.AsyncSession; +import org.neo4j.driver.exceptions.UnsupportedFeatureException; import org.neo4j.driver.reactive.ReactiveSession; import org.neo4j.driver.reactive.RxSession; import org.neo4j.driver.util.Experimental; @@ -65,6 +66,10 @@ public final class SessionConfig implements Serializable { * The bookmark manager. */ private final BookmarkManager bookmarkManager; + /** + * The notification config. + */ + private final NotificationConfig notificationConfig; private SessionConfig(Builder builder) { this.bookmarks = builder.bookmarks; @@ -73,6 +78,7 @@ private SessionConfig(Builder builder) { this.fetchSize = builder.fetchSize; this.impersonatedUser = builder.impersonatedUser; this.bookmarkManager = builder.bookmarkManager; + this.notificationConfig = builder.notificationConfig; } /** @@ -161,6 +167,15 @@ public Optional bookmarkManager() { return Optional.ofNullable(bookmarkManager); } + /** + * Returns notification config. + * @return the notification config + * @since 5.7 + */ + public NotificationConfig notificationConfig() { + return notificationConfig; + } + @Override public boolean equals(Object o) { if (this == o) { @@ -175,7 +190,8 @@ public boolean equals(Object o) { && Objects.equals(database, that.database) && Objects.equals(fetchSize, that.fetchSize) && Objects.equals(impersonatedUser, that.impersonatedUser) - && Objects.equals(bookmarkManager, that.bookmarkManager); + && Objects.equals(bookmarkManager, that.bookmarkManager) + && Objects.equals(notificationConfig, that.notificationConfig); } @Override @@ -203,6 +219,7 @@ public static final class Builder { private String database = null; private String impersonatedUser = null; private BookmarkManager bookmarkManager; + private NotificationConfig notificationConfig = NotificationConfig.defaultConfig(); private Builder() {} @@ -366,6 +383,22 @@ public Builder withBookmarkManager(BookmarkManager bookmarkManager) { return this; } + /** + * Sets notification config. + *

+ * Any configuration other than the {@link NotificationConfig#defaultConfig()} requires a minimum Bolt protocol + * version 5.2. Otherwise, an {@link UnsupportedFeatureException} will be emitted when the driver comes across a + * Bolt connection that does not support this feature. For instance, when running a query. + * + * @param notificationConfig the notification config + * @return this builder + * @since 5.7 + */ + public Builder withNotificationConfig(NotificationConfig notificationConfig) { + this.notificationConfig = Objects.requireNonNull(notificationConfig, "notificationConfig must not be null"); + return this; + } + /** * Builds the {@link SessionConfig}. * @return the config diff --git a/driver/src/main/java/org/neo4j/driver/exceptions/UnsupportedFeatureException.java b/driver/src/main/java/org/neo4j/driver/exceptions/UnsupportedFeatureException.java new file mode 100644 index 0000000000..45c4d945ad --- /dev/null +++ b/driver/src/main/java/org/neo4j/driver/exceptions/UnsupportedFeatureException.java @@ -0,0 +1,47 @@ +/* + * Copyright (c) "Neo4j" + * Neo4j Sweden AB [http://neo4j.com] + * + * This file is part of Neo4j. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.neo4j.driver.exceptions; + +import java.io.Serial; + +/** + * A feature is not supported in a given setup. + * @since 5.7 + */ +public class UnsupportedFeatureException extends ClientException { + @Serial + private static final long serialVersionUID = 1161333003602398544L; + + /** + * Constructs a new instance. + * @param message the message + */ + public UnsupportedFeatureException(String message) { + super(message); + } + + /** + * Constructs a new instance. + * @param message the message + * @param cause the cause + */ + public UnsupportedFeatureException(String message, Throwable cause) { + super(message, cause); + } +} 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 1aa87743bd..8a9056311f 100644 --- a/driver/src/main/java/org/neo4j/driver/internal/DriverFactory.java +++ b/driver/src/main/java/org/neo4j/driver/internal/DriverFactory.java @@ -179,7 +179,13 @@ protected ChannelConnector createConnector( Clock clock, RoutingContext routingContext) { return new ChannelConnectorImpl( - settings, securityPlan, config.logging(), clock, routingContext, getDomainNameResolver()); + settings, + securityPlan, + config.logging(), + clock, + routingContext, + getDomainNameResolver(), + config.notificationConfig()); } private InternalDriver createDriver( diff --git a/driver/src/main/java/org/neo4j/driver/internal/InternalNotificationCategory.java b/driver/src/main/java/org/neo4j/driver/internal/InternalNotificationCategory.java new file mode 100644 index 0000000000..e63c509d68 --- /dev/null +++ b/driver/src/main/java/org/neo4j/driver/internal/InternalNotificationCategory.java @@ -0,0 +1,54 @@ +/* + * Copyright (c) "Neo4j" + * Neo4j Sweden AB [http://neo4j.com] + * + * This file is part of Neo4j. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.neo4j.driver.internal; + +import java.util.Arrays; +import java.util.Objects; +import java.util.Optional; +import org.neo4j.driver.NotificationCategory; + +public record InternalNotificationCategory(Type type) implements NotificationCategory { + + public InternalNotificationCategory { + Objects.requireNonNull(type, "type must not be null"); + } + + public enum Type { + HINT, + UNRECOGNIZED, + UNSUPPORTED, + PERFORMANCE, + DEPRECATION, + GENERIC; + } + + public static Optional valueOf(String value) { + return Arrays.stream(Type.values()) + .filter(type -> type.toString().equals(value)) + .findFirst() + .map(type -> switch (type) { + case HINT -> NotificationCategory.HINT; + case UNRECOGNIZED -> NotificationCategory.UNRECOGNIZED; + case UNSUPPORTED -> NotificationCategory.UNSUPPORTED; + case PERFORMANCE -> NotificationCategory.PERFORMANCE; + case DEPRECATION -> NotificationCategory.DEPRECATION; + case GENERIC -> NotificationCategory.GENERIC; + }); + } +} diff --git a/driver/src/main/java/org/neo4j/driver/internal/InternalNotificationConfig.java b/driver/src/main/java/org/neo4j/driver/internal/InternalNotificationConfig.java new file mode 100644 index 0000000000..4949fba500 --- /dev/null +++ b/driver/src/main/java/org/neo4j/driver/internal/InternalNotificationConfig.java @@ -0,0 +1,41 @@ +/* + * Copyright (c) "Neo4j" + * Neo4j Sweden AB [http://neo4j.com] + * + * This file is part of Neo4j. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.neo4j.driver.internal; + +import java.util.Objects; +import java.util.Set; +import org.neo4j.driver.NotificationCategory; +import org.neo4j.driver.NotificationConfig; +import org.neo4j.driver.NotificationSeverity; + +public record InternalNotificationConfig( + NotificationSeverity minimumSeverity, Set disabledCategories) + implements NotificationConfig { + @Override + public NotificationConfig enableMinimumSeverity(NotificationSeverity minimumSeverity) { + Objects.requireNonNull(minimumSeverity, "minimumSeverity must not be null"); + return new InternalNotificationConfig(minimumSeverity, disabledCategories); + } + + @Override + public NotificationConfig disableCategories(Set disabledCategories) { + Objects.requireNonNull(disabledCategories, "disabledCategories must not be null"); + return new InternalNotificationConfig(minimumSeverity, Set.copyOf(disabledCategories)); + } +} diff --git a/driver/src/main/java/org/neo4j/driver/internal/InternalNotificationSeverity.java b/driver/src/main/java/org/neo4j/driver/internal/InternalNotificationSeverity.java new file mode 100644 index 0000000000..935872425e --- /dev/null +++ b/driver/src/main/java/org/neo4j/driver/internal/InternalNotificationSeverity.java @@ -0,0 +1,55 @@ +/* + * Copyright (c) "Neo4j" + * Neo4j Sweden AB [http://neo4j.com] + * + * This file is part of Neo4j. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.neo4j.driver.internal; + +import java.util.Arrays; +import java.util.Objects; +import java.util.Optional; +import org.neo4j.driver.NotificationSeverity; + +public record InternalNotificationSeverity(Type type, int level) implements NotificationSeverity { + public static final InternalNotificationSeverity OFF = + new InternalNotificationSeverity(Type.OFF, Integer.MAX_VALUE); + + public InternalNotificationSeverity { + Objects.requireNonNull(type, "type must not be null"); + } + + @Override + public int compareTo(NotificationSeverity severity) { + return Integer.compare(level, ((InternalNotificationSeverity) severity).level()); + } + + public enum Type { + INFORMATION, + WARNING, + OFF + } + + public static Optional valueOf(String value) { + return Arrays.stream(Type.values()) + .filter(type -> type.toString().equals(value)) + .findFirst() + .map(type -> switch (type) { + case INFORMATION -> NotificationSeverity.INFORMATION; + case WARNING -> NotificationSeverity.WARNING; + case OFF -> InternalNotificationSeverity.OFF; + }); + } +} 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 7fd41280ad..737ebd8d3d 100644 --- a/driver/src/main/java/org/neo4j/driver/internal/SessionFactoryImpl.java +++ b/driver/src/main/java/org/neo4j/driver/internal/SessionFactoryImpl.java @@ -29,6 +29,7 @@ import org.neo4j.driver.BookmarkManager; import org.neo4j.driver.Config; import org.neo4j.driver.Logging; +import org.neo4j.driver.NotificationConfig; import org.neo4j.driver.SessionConfig; import org.neo4j.driver.internal.async.LeakLoggingNetworkSession; import org.neo4j.driver.internal.async.NetworkSession; @@ -41,6 +42,7 @@ public class SessionFactoryImpl implements SessionFactory { private final Logging logging; private final boolean leakedSessionsLoggingEnabled; private final long defaultFetchSize; + private final NotificationConfig driverNotificationConfig; SessionFactoryImpl(ConnectionProvider connectionProvider, RetryLogic retryLogic, Config config) { this.connectionProvider = connectionProvider; @@ -48,6 +50,7 @@ public class SessionFactoryImpl implements SessionFactory { this.retryLogic = retryLogic; this.logging = config.logging(); this.defaultFetchSize = config.fetchSize(); + this.driverNotificationConfig = config.notificationConfig(); } @Override @@ -61,7 +64,8 @@ public NetworkSession newInstance(SessionConfig sessionConfig) { parseFetchSize(sessionConfig), sessionConfig.impersonatedUser().orElse(null), logging, - sessionConfig.bookmarkManager().orElse(NoOpBookmarkManager.INSTANCE)); + sessionConfig.bookmarkManager().orElse(NoOpBookmarkManager.INSTANCE), + sessionConfig.notificationConfig()); } private Set toDistinctSet(Iterable bookmarks) { @@ -131,7 +135,8 @@ private NetworkSession createSession( long fetchSize, String impersonatedUser, Logging logging, - BookmarkManager bookmarkManager) { + BookmarkManager bookmarkManager, + NotificationConfig notificationConfig) { Objects.requireNonNull(bookmarks, "bookmarks may not be null"); Objects.requireNonNull(bookmarkManager, "bookmarkManager may not be null"); return leakedSessionsLoggingEnabled @@ -144,7 +149,8 @@ private NetworkSession createSession( impersonatedUser, fetchSize, logging, - bookmarkManager) + bookmarkManager, + notificationConfig) : new NetworkSession( connectionProvider, retryLogic, @@ -154,6 +160,7 @@ private NetworkSession createSession( impersonatedUser, fetchSize, logging, - bookmarkManager); + bookmarkManager, + notificationConfig); } } 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 86985059ee..266fbb61b4 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 @@ -25,6 +25,7 @@ import org.neo4j.driver.Bookmark; import org.neo4j.driver.BookmarkManager; import org.neo4j.driver.Logging; +import org.neo4j.driver.NotificationConfig; import org.neo4j.driver.internal.DatabaseName; import org.neo4j.driver.internal.retry.RetryLogic; import org.neo4j.driver.internal.spi.ConnectionProvider; @@ -42,7 +43,8 @@ public LeakLoggingNetworkSession( String impersonatedUser, long fetchSize, Logging logging, - BookmarkManager bookmarkManager) { + BookmarkManager bookmarkManager, + NotificationConfig notificationConfig) { super( connectionProvider, retryLogic, @@ -52,7 +54,8 @@ public LeakLoggingNetworkSession( impersonatedUser, fetchSize, logging, - bookmarkManager); + bookmarkManager, + notificationConfig); 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 016df32f30..79cad9010e 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 @@ -36,6 +36,7 @@ import org.neo4j.driver.BookmarkManager; import org.neo4j.driver.Logger; import org.neo4j.driver.Logging; +import org.neo4j.driver.NotificationConfig; import org.neo4j.driver.Query; import org.neo4j.driver.TransactionConfig; import org.neo4j.driver.async.ResultCursor; @@ -75,6 +76,7 @@ public class NetworkSession { private final BookmarkManager bookmarkManager; private volatile Set lastUsedBookmarks = Collections.emptySet(); private volatile Set lastReceivedBookmarks; + private final NotificationConfig notificationConfig; public NetworkSession( ConnectionProvider connectionProvider, @@ -85,7 +87,8 @@ public NetworkSession( String impersonatedUser, long fetchSize, Logging logging, - BookmarkManager bookmarkManager) { + BookmarkManager bookmarkManager, + NotificationConfig notificationConfig) { Objects.requireNonNull(bookmarks, "bookmarks may not be null"); Objects.requireNonNull(bookmarkManager, "bookmarkManager may not be null"); this.connectionProvider = connectionProvider; @@ -101,6 +104,7 @@ public NetworkSession( this.connectionContext = new NetworkSessionConnectionContext(databaseNameFuture, determineBookmarks(false), impersonatedUser); this.fetchSize = fetchSize; + this.notificationConfig = notificationConfig; } public CompletionStage runAsync(Query query, TransactionConfig config) { @@ -143,7 +147,8 @@ public CompletionStage beginTransactionAsync( .thenApply(connection -> ImpersonationUtil.ensureImpersonationSupport(connection, connection.impersonatedUser())) .thenCompose(connection -> { - UnmanagedTransaction tx = new UnmanagedTransaction(connection, this::handleNewBookmark, fetchSize); + UnmanagedTransaction tx = new UnmanagedTransaction( + connection, this::handleNewBookmark, fetchSize, notificationConfig); return tx.beginAsync(determineBookmarks(true), config, txType); }); @@ -259,7 +264,8 @@ private CompletionStage buildResultCursorFactory(Query quer determineBookmarks(true), this::handleNewBookmark, config, - fetchSize); + fetchSize, + notificationConfig); return completedFuture(factory); } catch (Throwable e) { return Futures.failedFuture(e); 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 677079c7fb..69786ece57 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 @@ -37,6 +37,7 @@ import java.util.function.Consumer; import java.util.function.Function; import org.neo4j.driver.Bookmark; +import org.neo4j.driver.NotificationConfig; import org.neo4j.driver.Query; import org.neo4j.driver.TransactionConfig; import org.neo4j.driver.async.ResultCursor; @@ -93,26 +94,33 @@ private enum State { private CompletableFuture rollbackFuture; private Throwable causeOfTermination; private CompletionStage interruptStage; + private final NotificationConfig notificationConfig; - public UnmanagedTransaction(Connection connection, Consumer bookmarkConsumer, long fetchSize) { - this(connection, bookmarkConsumer, fetchSize, new ResultCursorsHolder()); + public UnmanagedTransaction( + Connection connection, + Consumer bookmarkConsumer, + long fetchSize, + NotificationConfig notificationConfig) { + this(connection, bookmarkConsumer, fetchSize, new ResultCursorsHolder(), notificationConfig); } protected UnmanagedTransaction( Connection connection, Consumer bookmarkConsumer, long fetchSize, - ResultCursorsHolder resultCursors) { + ResultCursorsHolder resultCursors, + NotificationConfig notificationConfig) { this.connection = connection; this.protocol = connection.protocol(); this.bookmarkConsumer = bookmarkConsumer; this.resultCursors = resultCursors; this.fetchSize = fetchSize; + this.notificationConfig = notificationConfig; } public CompletionStage beginAsync( Set initialBookmarks, TransactionConfig config, String txType) { - return protocol.beginTransaction(connection, initialBookmarks, config, txType) + return protocol.beginTransaction(connection, initialBookmarks, config, txType, notificationConfig) .handle((ignore, beginError) -> { if (beginError != null) { if (beginError instanceof AuthorizationExpiredException) { diff --git a/driver/src/main/java/org/neo4j/driver/internal/async/connection/BoltProtocolUtil.java b/driver/src/main/java/org/neo4j/driver/internal/async/connection/BoltProtocolUtil.java index 1ff165865b..c87d2cab7e 100644 --- a/driver/src/main/java/org/neo4j/driver/internal/async/connection/BoltProtocolUtil.java +++ b/driver/src/main/java/org/neo4j/driver/internal/async/connection/BoltProtocolUtil.java @@ -29,6 +29,7 @@ import org.neo4j.driver.internal.messaging.v42.BoltProtocolV42; import org.neo4j.driver.internal.messaging.v44.BoltProtocolV44; import org.neo4j.driver.internal.messaging.v5.BoltProtocolV5; +import org.neo4j.driver.internal.messaging.v52.BoltProtocolV52; public final class BoltProtocolUtil { public static final int BOLT_MAGIC_PREAMBLE = 0x6060B017; @@ -40,7 +41,7 @@ public final class BoltProtocolUtil { private static final ByteBuf HANDSHAKE_BUF = unreleasableBuffer(copyInt( BOLT_MAGIC_PREAMBLE, - BoltProtocolV5.VERSION.toInt(), + BoltProtocolV52.VERSION.toIntRange(BoltProtocolV5.VERSION), BoltProtocolV44.VERSION.toIntRange(BoltProtocolV42.VERSION), BoltProtocolV41.VERSION.toInt(), BoltProtocolV3.VERSION.toInt())) diff --git a/driver/src/main/java/org/neo4j/driver/internal/async/connection/ChannelConnectorImpl.java b/driver/src/main/java/org/neo4j/driver/internal/async/connection/ChannelConnectorImpl.java index 35a6ffc08d..4f76e8a79c 100644 --- a/driver/src/main/java/org/neo4j/driver/internal/async/connection/ChannelConnectorImpl.java +++ b/driver/src/main/java/org/neo4j/driver/internal/async/connection/ChannelConnectorImpl.java @@ -32,6 +32,7 @@ import java.time.Clock; import org.neo4j.driver.AuthToken; import org.neo4j.driver.Logging; +import org.neo4j.driver.NotificationConfig; import org.neo4j.driver.internal.BoltServerAddress; import org.neo4j.driver.internal.ConnectionSettings; import org.neo4j.driver.internal.DomainNameResolver; @@ -50,6 +51,7 @@ public class ChannelConnectorImpl implements ChannelConnector { private final Clock clock; private final DomainNameResolver domainNameResolver; private final AddressResolverGroup addressResolverGroup; + private final NotificationConfig notificationConfig; public ChannelConnectorImpl( ConnectionSettings connectionSettings, @@ -57,7 +59,8 @@ public ChannelConnectorImpl( Logging logging, Clock clock, RoutingContext routingContext, - DomainNameResolver domainNameResolver) { + DomainNameResolver domainNameResolver, + NotificationConfig notificationConfig) { this( connectionSettings, securityPlan, @@ -65,7 +68,8 @@ public ChannelConnectorImpl( logging, clock, routingContext, - domainNameResolver); + domainNameResolver, + notificationConfig); } public ChannelConnectorImpl( @@ -75,7 +79,8 @@ public ChannelConnectorImpl( Logging logging, Clock clock, RoutingContext routingContext, - DomainNameResolver domainNameResolver) { + DomainNameResolver domainNameResolver, + NotificationConfig notificationConfig) { this.userAgent = connectionSettings.userAgent(); this.authToken = connectionSettings.authToken(); this.routingContext = routingContext; @@ -86,6 +91,7 @@ public ChannelConnectorImpl( this.clock = requireNonNull(clock); this.domainNameResolver = requireNonNull(domainNameResolver); this.addressResolverGroup = new NettyDomainNameResolverGroup(this.domainNameResolver); + this.notificationConfig = notificationConfig; } @Override @@ -137,7 +143,7 @@ private void installHandshakeCompletedListeners( // add listener that sends an INIT message. connection is now fully established. channel pipeline if fully // set to send/receive messages for a selected protocol version - handshakeCompleted.addListener( - new HandshakeCompletedListener(userAgent, authToken, routingContext, connectionInitialized)); + handshakeCompleted.addListener(new HandshakeCompletedListener( + userAgent, authToken, routingContext, connectionInitialized, notificationConfig)); } } diff --git a/driver/src/main/java/org/neo4j/driver/internal/async/connection/HandshakeCompletedListener.java b/driver/src/main/java/org/neo4j/driver/internal/async/connection/HandshakeCompletedListener.java index 2a9f3e5953..3fdda59ca9 100644 --- a/driver/src/main/java/org/neo4j/driver/internal/async/connection/HandshakeCompletedListener.java +++ b/driver/src/main/java/org/neo4j/driver/internal/async/connection/HandshakeCompletedListener.java @@ -24,6 +24,7 @@ import io.netty.channel.ChannelFutureListener; import io.netty.channel.ChannelPromise; import org.neo4j.driver.AuthToken; +import org.neo4j.driver.NotificationConfig; import org.neo4j.driver.internal.cluster.RoutingContext; import org.neo4j.driver.internal.messaging.BoltProtocol; @@ -32,23 +33,27 @@ public class HandshakeCompletedListener implements ChannelFutureListener { private final AuthToken authToken; private final RoutingContext routingContext; private final ChannelPromise connectionInitializedPromise; + private final NotificationConfig notificationConfig; public HandshakeCompletedListener( String userAgent, AuthToken authToken, RoutingContext routingContext, - ChannelPromise connectionInitializedPromise) { + ChannelPromise connectionInitializedPromise, + NotificationConfig notificationConfig) { this.userAgent = requireNonNull(userAgent); this.authToken = requireNonNull(authToken); this.routingContext = routingContext; this.connectionInitializedPromise = requireNonNull(connectionInitializedPromise); + this.notificationConfig = notificationConfig; } @Override public void operationComplete(ChannelFuture future) { if (future.isSuccess()) { BoltProtocol protocol = BoltProtocol.forChannel(future.channel()); - protocol.initializeChannel(userAgent, authToken, routingContext, connectionInitializedPromise); + protocol.initializeChannel( + userAgent, authToken, routingContext, connectionInitializedPromise, notificationConfig); } else { connectionInitializedPromise.setFailure(future.cause()); } diff --git a/driver/src/main/java/org/neo4j/driver/internal/cluster/RediscoveryImpl.java b/driver/src/main/java/org/neo4j/driver/internal/cluster/RediscoveryImpl.java index 9d58ed32f2..4b4c0524d0 100644 --- a/driver/src/main/java/org/neo4j/driver/internal/cluster/RediscoveryImpl.java +++ b/driver/src/main/java/org/neo4j/driver/internal/cluster/RediscoveryImpl.java @@ -43,6 +43,7 @@ import org.neo4j.driver.exceptions.FatalDiscoveryException; import org.neo4j.driver.exceptions.SecurityException; import org.neo4j.driver.exceptions.ServiceUnavailableException; +import org.neo4j.driver.exceptions.UnsupportedFeatureException; import org.neo4j.driver.internal.BoltServerAddress; import org.neo4j.driver.internal.DomainNameResolver; import org.neo4j.driver.internal.ImpersonationUtil; @@ -296,6 +297,8 @@ private boolean mustAbortDiscovery(Throwable throwable) { } else if (throwable instanceof IllegalStateException && ConnectionPool.CONNECTION_POOL_CLOSED_ERROR_MESSAGE.equals(throwable.getMessage())) { abort = true; + } else if (throwable instanceof UnsupportedFeatureException) { + abort = true; } else if (throwable instanceof ClientException) { String code = ((ClientException) throwable).code(); abort = switch (code) { diff --git a/driver/src/main/java/org/neo4j/driver/internal/cluster/SingleDatabaseRoutingProcedureRunner.java b/driver/src/main/java/org/neo4j/driver/internal/cluster/SingleDatabaseRoutingProcedureRunner.java index 3a8e424694..127e71069e 100644 --- a/driver/src/main/java/org/neo4j/driver/internal/cluster/SingleDatabaseRoutingProcedureRunner.java +++ b/driver/src/main/java/org/neo4j/driver/internal/cluster/SingleDatabaseRoutingProcedureRunner.java @@ -92,7 +92,8 @@ CompletionStage> runProcedure(Connection connection, Query procedur bookmarks, (ignored) -> {}, TransactionConfig.empty(), - UNLIMITED_FETCH_SIZE) + UNLIMITED_FETCH_SIZE, + null) .asyncResult() .thenCompose(ResultCursor::listAsync); } diff --git a/driver/src/main/java/org/neo4j/driver/internal/handlers/LogonResponseHandler.java b/driver/src/main/java/org/neo4j/driver/internal/handlers/LogonResponseHandler.java new file mode 100644 index 0000000000..acbad6fc42 --- /dev/null +++ b/driver/src/main/java/org/neo4j/driver/internal/handlers/LogonResponseHandler.java @@ -0,0 +1,52 @@ +/* + * Copyright (c) "Neo4j" + * Neo4j Sweden AB [http://neo4j.com] + * + * This file is part of Neo4j. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.neo4j.driver.internal.handlers; + +import io.netty.channel.Channel; +import io.netty.channel.ChannelPromise; +import java.util.Map; +import org.neo4j.driver.Value; +import org.neo4j.driver.exceptions.ProtocolException; +import org.neo4j.driver.internal.spi.ResponseHandler; + +public class LogonResponseHandler implements ResponseHandler { + + private final ChannelPromise connectionInitializedPromise; + private final Channel channel; + + public LogonResponseHandler(ChannelPromise connectionInitializedPromise) { + this.connectionInitializedPromise = connectionInitializedPromise; + this.channel = connectionInitializedPromise.channel(); + } + + @Override + public void onSuccess(Map metadata) { + connectionInitializedPromise.setSuccess(); + } + + @Override + public void onFailure(Throwable error) { + channel.close().addListener(future -> connectionInitializedPromise.setFailure(error)); + } + + @Override + public void onRecord(Value[] fields) { + throw new ProtocolException("records not supported"); + } +} diff --git a/driver/src/main/java/org/neo4j/driver/internal/messaging/BoltProtocol.java b/driver/src/main/java/org/neo4j/driver/internal/messaging/BoltProtocol.java index 315078606e..0e55baffc2 100644 --- a/driver/src/main/java/org/neo4j/driver/internal/messaging/BoltProtocol.java +++ b/driver/src/main/java/org/neo4j/driver/internal/messaging/BoltProtocol.java @@ -27,6 +27,7 @@ import java.util.function.Consumer; import org.neo4j.driver.AuthToken; import org.neo4j.driver.Bookmark; +import org.neo4j.driver.NotificationConfig; import org.neo4j.driver.Query; import org.neo4j.driver.Session; import org.neo4j.driver.Transaction; @@ -43,6 +44,8 @@ import org.neo4j.driver.internal.messaging.v43.BoltProtocolV43; import org.neo4j.driver.internal.messaging.v44.BoltProtocolV44; import org.neo4j.driver.internal.messaging.v5.BoltProtocolV5; +import org.neo4j.driver.internal.messaging.v51.BoltProtocolV51; +import org.neo4j.driver.internal.messaging.v52.BoltProtocolV52; import org.neo4j.driver.internal.spi.Connection; public interface BoltProtocol { @@ -56,16 +59,18 @@ public interface BoltProtocol { /** * Initialize channel after it is connected and handshake selected this protocol version. * - * @param userAgent the user agent string. - * @param authToken the authentication token. - * @param routingContext the configured routing context + * @param userAgent the user agent string. + * @param authToken the authentication token. + * @param routingContext the configured routing context * @param channelInitializedPromise the promise to be notified when initialization is completed. + * @param notificationConfig the notification configuration */ void initializeChannel( String userAgent, AuthToken authToken, RoutingContext routingContext, - ChannelPromise channelInitializedPromise); + ChannelPromise channelInitializedPromise, + NotificationConfig notificationConfig); /** * Prepare to close channel before it is closed. @@ -76,14 +81,19 @@ void initializeChannel( /** * Begin an unmanaged transaction. * - * @param connection the connection to use. - * @param bookmarks the bookmarks. Never null, should be empty when there are no bookmarks. - * @param config the transaction configuration. Never null, should be {@link TransactionConfig#empty()} when absent. - * @param txType the Kernel transaction type + * @param connection the connection to use. + * @param bookmarks the bookmarks. Never null, should be empty when there are no bookmarks. + * @param config the transaction configuration. Never null, should be {@link TransactionConfig#empty()} when absent. + * @param txType the Kernel transaction type + * @param notificationConfig the notification configuration * @return a completion stage completed when transaction is started or completed exceptionally when there was a failure. */ CompletionStage beginTransaction( - Connection connection, Set bookmarks, TransactionConfig config, String txType); + Connection connection, + Set bookmarks, + TransactionConfig config, + String txType, + NotificationConfig notificationConfig); /** * Commit the unmanaged transaction. @@ -104,11 +114,12 @@ CompletionStage beginTransaction( /** * Execute the given query in an auto-commit transaction, i.e. {@link Session#run(Query)}. * - * @param connection the network connection to use. - * @param query the cypher to execute. - * @param bookmarkConsumer the database bookmark consumer. - * @param config the transaction config for the implicitly started auto-commit transaction. - * @param fetchSize the record fetch size for PULL message. + * @param connection the network connection to use. + * @param query the cypher to execute. + * @param bookmarkConsumer the database bookmark consumer. + * @param config the transaction config for the implicitly started auto-commit transaction. + * @param fetchSize the record fetch size for PULL message. + * @param notificationConfig the notification configuration * @return stage with cursor. */ ResultCursorFactory runInAutoCommitTransaction( @@ -117,7 +128,8 @@ ResultCursorFactory runInAutoCommitTransaction( Set bookmarks, Consumer bookmarkConsumer, TransactionConfig config, - long fetchSize); + long fetchSize, + NotificationConfig notificationConfig); /** * Execute the given query in a running unmanaged transaction, i.e. {@link Transaction#run(Query)}. @@ -170,6 +182,10 @@ static BoltProtocol forVersion(BoltProtocolVersion version) { return BoltProtocolV44.INSTANCE; } else if (BoltProtocolV5.VERSION.equals(version)) { return BoltProtocolV5.INSTANCE; + } else if (BoltProtocolV51.VERSION.equals(version)) { + return BoltProtocolV51.INSTANCE; + } else if (BoltProtocolV52.VERSION.equals(version)) { + return BoltProtocolV52.INSTANCE; } throw new ClientException("Unknown protocol version: " + version); } diff --git a/driver/src/main/java/org/neo4j/driver/internal/messaging/encode/LogonMessageEncoder.java b/driver/src/main/java/org/neo4j/driver/internal/messaging/encode/LogonMessageEncoder.java new file mode 100644 index 0000000000..d5e4805f6e --- /dev/null +++ b/driver/src/main/java/org/neo4j/driver/internal/messaging/encode/LogonMessageEncoder.java @@ -0,0 +1,37 @@ +/* + * Copyright (c) "Neo4j" + * Neo4j Sweden AB [http://neo4j.com] + * + * This file is part of Neo4j. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.neo4j.driver.internal.messaging.encode; + +import static org.neo4j.driver.internal.util.Preconditions.checkArgument; + +import java.io.IOException; +import org.neo4j.driver.internal.messaging.Message; +import org.neo4j.driver.internal.messaging.MessageEncoder; +import org.neo4j.driver.internal.messaging.ValuePacker; +import org.neo4j.driver.internal.messaging.request.LogonMessage; + +public class LogonMessageEncoder implements MessageEncoder { + @Override + public void encode(Message message, ValuePacker packer) throws IOException { + checkArgument(message, LogonMessage.class); + var logonMessage = (LogonMessage) message; + packer.packStructHeader(1, logonMessage.signature()); + packer.pack(logonMessage.metadata()); + } +} diff --git a/driver/src/main/java/org/neo4j/driver/internal/messaging/request/BeginMessage.java b/driver/src/main/java/org/neo4j/driver/internal/messaging/request/BeginMessage.java index bdc4180e69..0b599a3169 100644 --- a/driver/src/main/java/org/neo4j/driver/internal/messaging/request/BeginMessage.java +++ b/driver/src/main/java/org/neo4j/driver/internal/messaging/request/BeginMessage.java @@ -26,6 +26,7 @@ import java.util.Set; import org.neo4j.driver.AccessMode; import org.neo4j.driver.Bookmark; +import org.neo4j.driver.NotificationConfig; import org.neo4j.driver.TransactionConfig; import org.neo4j.driver.Value; import org.neo4j.driver.internal.DatabaseName; @@ -39,8 +40,17 @@ public BeginMessage( DatabaseName databaseName, AccessMode mode, String impersonatedUser, - String txType) { - this(bookmarks, config.timeout(), config.metadata(), mode, databaseName, impersonatedUser, txType); + String txType, + NotificationConfig notificationConfig) { + this( + bookmarks, + config.timeout(), + config.metadata(), + mode, + databaseName, + impersonatedUser, + txType, + notificationConfig); } public BeginMessage( @@ -50,8 +60,10 @@ public BeginMessage( AccessMode mode, DatabaseName databaseName, String impersonatedUser, - String txType) { - super(buildMetadata(txTimeout, txMetadata, databaseName, mode, bookmarks, impersonatedUser, txType)); + String txType, + NotificationConfig notificationConfig) { + super(buildMetadata( + txTimeout, txMetadata, databaseName, mode, bookmarks, impersonatedUser, txType, notificationConfig)); } @Override diff --git a/driver/src/main/java/org/neo4j/driver/internal/messaging/request/HelloMessage.java b/driver/src/main/java/org/neo4j/driver/internal/messaging/request/HelloMessage.java index 4d0abe8859..846632350d 100644 --- a/driver/src/main/java/org/neo4j/driver/internal/messaging/request/HelloMessage.java +++ b/driver/src/main/java/org/neo4j/driver/internal/messaging/request/HelloMessage.java @@ -25,6 +25,7 @@ import java.util.HashMap; import java.util.Map; import java.util.Objects; +import org.neo4j.driver.NotificationConfig; import org.neo4j.driver.Value; public class HelloMessage extends MessageWithMetadata { @@ -40,8 +41,9 @@ public HelloMessage( String userAgent, Map authToken, Map routingContext, - boolean includeDateTimeUtc) { - super(buildMetadata(userAgent, authToken, routingContext, includeDateTimeUtc)); + boolean includeDateTimeUtc, + NotificationConfig notificationConfig) { + super(buildMetadata(userAgent, authToken, routingContext, includeDateTimeUtc, notificationConfig)); } @Override @@ -77,7 +79,8 @@ private static Map buildMetadata( String userAgent, Map authToken, Map routingContext, - boolean includeDateTimeUtc) { + boolean includeDateTimeUtc, + NotificationConfig notificationConfig) { Map result = new HashMap<>(authToken); result.put(USER_AGENT_METADATA_KEY, value(userAgent)); if (routingContext != null) { @@ -86,6 +89,7 @@ private static Map buildMetadata( if (includeDateTimeUtc) { result.put(PATCH_BOLT_METADATA_KEY, value(Collections.singleton(DATE_TIME_UTC_PATCH_VALUE))); } + MessageWithMetadata.appendNotificationConfig(result, notificationConfig); return result; } } diff --git a/driver/src/main/java/org/neo4j/driver/internal/messaging/request/LogonMessage.java b/driver/src/main/java/org/neo4j/driver/internal/messaging/request/LogonMessage.java new file mode 100644 index 0000000000..8cbe4c45ed --- /dev/null +++ b/driver/src/main/java/org/neo4j/driver/internal/messaging/request/LogonMessage.java @@ -0,0 +1,46 @@ +/* + * Copyright (c) "Neo4j" + * Neo4j Sweden AB [http://neo4j.com] + * + * This file is part of Neo4j. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.neo4j.driver.internal.messaging.request; + +import static org.neo4j.driver.Values.value; +import static org.neo4j.driver.internal.security.InternalAuthToken.CREDENTIALS_KEY; + +import java.util.HashMap; +import java.util.Map; +import org.neo4j.driver.Value; + +public class LogonMessage extends MessageWithMetadata { + public static final byte SIGNATURE = 0x6A; + + public LogonMessage(Map authToken) { + super(authToken); + } + + @Override + public byte signature() { + return SIGNATURE; + } + + @Override + public String toString() { + Map metadataCopy = new HashMap<>(metadata()); + metadataCopy.replace(CREDENTIALS_KEY, value("******")); + return "LOGON " + metadataCopy; + } +} diff --git a/driver/src/main/java/org/neo4j/driver/internal/messaging/request/MessageWithMetadata.java b/driver/src/main/java/org/neo4j/driver/internal/messaging/request/MessageWithMetadata.java index 76aa05be48..3433dc0087 100644 --- a/driver/src/main/java/org/neo4j/driver/internal/messaging/request/MessageWithMetadata.java +++ b/driver/src/main/java/org/neo4j/driver/internal/messaging/request/MessageWithMetadata.java @@ -18,11 +18,19 @@ */ package org.neo4j.driver.internal.messaging.request; +import static org.neo4j.driver.Values.value; + import java.util.Map; +import org.neo4j.driver.NotificationConfig; import org.neo4j.driver.Value; +import org.neo4j.driver.internal.InternalNotificationCategory; +import org.neo4j.driver.internal.InternalNotificationConfig; +import org.neo4j.driver.internal.InternalNotificationSeverity; import org.neo4j.driver.internal.messaging.Message; abstract class MessageWithMetadata implements Message { + static final String NOTIFICATIONS_MINIMUM_SEVERITY = "notifications_minimum_severity"; + static final String NOTIFICATIONS_DISABLED_CATEGORIES = "notifications_disabled_categories"; private final Map metadata; public MessageWithMetadata(Map metadata) { @@ -32,4 +40,25 @@ public MessageWithMetadata(Map metadata) { public Map metadata() { return metadata; } + + static void appendNotificationConfig(Map result, NotificationConfig config) { + if (config != null) { + if (config instanceof InternalNotificationConfig internalConfig) { + var severity = (InternalNotificationSeverity) internalConfig.minimumSeverity(); + if (severity != null) { + result.put( + NOTIFICATIONS_MINIMUM_SEVERITY, + value(severity.type().toString())); + } + var disabledCategories = internalConfig.disabledCategories(); + if (disabledCategories != null) { + var list = disabledCategories.stream() + .map(category -> (InternalNotificationCategory) category) + .map(category -> category.type().toString()) + .toList(); + result.put(NOTIFICATIONS_DISABLED_CATEGORIES, value(list)); + } + } + } + } } diff --git a/driver/src/main/java/org/neo4j/driver/internal/messaging/request/RunWithMetadataMessage.java b/driver/src/main/java/org/neo4j/driver/internal/messaging/request/RunWithMetadataMessage.java index 50fd53f426..2bb7a1f677 100644 --- a/driver/src/main/java/org/neo4j/driver/internal/messaging/request/RunWithMetadataMessage.java +++ b/driver/src/main/java/org/neo4j/driver/internal/messaging/request/RunWithMetadataMessage.java @@ -28,6 +28,7 @@ import java.util.Set; import org.neo4j.driver.AccessMode; import org.neo4j.driver.Bookmark; +import org.neo4j.driver.NotificationConfig; import org.neo4j.driver.Query; import org.neo4j.driver.TransactionConfig; import org.neo4j.driver.Value; @@ -45,9 +46,17 @@ public static RunWithMetadataMessage autoCommitTxRunMessage( DatabaseName databaseName, AccessMode mode, Set bookmarks, - String impersonatedUser) { + String impersonatedUser, + NotificationConfig notificationConfig) { return autoCommitTxRunMessage( - query, config.timeout(), config.metadata(), databaseName, mode, bookmarks, impersonatedUser); + query, + config.timeout(), + config.metadata(), + databaseName, + mode, + bookmarks, + impersonatedUser, + notificationConfig); } public static RunWithMetadataMessage autoCommitTxRunMessage( @@ -57,9 +66,10 @@ public static RunWithMetadataMessage autoCommitTxRunMessage( DatabaseName databaseName, AccessMode mode, Set bookmarks, - String impersonatedUser) { - Map metadata = - buildMetadata(txTimeout, txMetadata, databaseName, mode, bookmarks, impersonatedUser, null); + String impersonatedUser, + NotificationConfig notificationConfig) { + Map metadata = buildMetadata( + txTimeout, txMetadata, databaseName, mode, bookmarks, impersonatedUser, null, notificationConfig); return new RunWithMetadataMessage(query.text(), query.parameters().asMap(ofValue()), metadata); } diff --git a/driver/src/main/java/org/neo4j/driver/internal/messaging/request/TransactionMetadataBuilder.java b/driver/src/main/java/org/neo4j/driver/internal/messaging/request/TransactionMetadataBuilder.java index 36ab5a1d0b..4fd7de2f98 100644 --- a/driver/src/main/java/org/neo4j/driver/internal/messaging/request/TransactionMetadataBuilder.java +++ b/driver/src/main/java/org/neo4j/driver/internal/messaging/request/TransactionMetadataBuilder.java @@ -27,6 +27,7 @@ import java.util.Set; import org.neo4j.driver.AccessMode; import org.neo4j.driver.Bookmark; +import org.neo4j.driver.NotificationConfig; import org.neo4j.driver.Value; import org.neo4j.driver.internal.DatabaseName; import org.neo4j.driver.internal.util.Iterables; @@ -48,7 +49,7 @@ public static Map buildMetadata( Set bookmarks, String impersonatedUser, String txType) { - return buildMetadata(txTimeout, txMetadata, defaultDatabase(), mode, bookmarks, impersonatedUser, txType); + return buildMetadata(txTimeout, txMetadata, defaultDatabase(), mode, bookmarks, impersonatedUser, txType, null); } public static Map buildMetadata( @@ -58,7 +59,8 @@ public static Map buildMetadata( AccessMode mode, Set bookmarks, String impersonatedUser, - String txType) { + String txType, + NotificationConfig notificationConfig) { boolean bookmarksPresent = !bookmarks.isEmpty(); boolean txTimeoutPresent = txTimeout != null; boolean txMetadataPresent = txMetadata != null && !txMetadata.isEmpty(); @@ -97,6 +99,7 @@ public static Map buildMetadata( if (txTypePresent) { result.put(TX_TYPE_KEY, value(txType)); } + MessageWithMetadata.appendNotificationConfig(result, notificationConfig); databaseName.databaseName().ifPresent(name -> result.put(DATABASE_NAME_KEY, value(name))); diff --git a/driver/src/main/java/org/neo4j/driver/internal/messaging/v3/BoltProtocolV3.java b/driver/src/main/java/org/neo4j/driver/internal/messaging/v3/BoltProtocolV3.java index 5919104f02..a5f964d777 100644 --- a/driver/src/main/java/org/neo4j/driver/internal/messaging/v3/BoltProtocolV3.java +++ b/driver/src/main/java/org/neo4j/driver/internal/messaging/v3/BoltProtocolV3.java @@ -34,8 +34,11 @@ import java.util.function.Consumer; import org.neo4j.driver.AuthToken; import org.neo4j.driver.Bookmark; +import org.neo4j.driver.NotificationConfig; import org.neo4j.driver.Query; import org.neo4j.driver.TransactionConfig; +import org.neo4j.driver.exceptions.Neo4jException; +import org.neo4j.driver.exceptions.UnsupportedFeatureException; import org.neo4j.driver.internal.DatabaseBookmark; import org.neo4j.driver.internal.DatabaseName; import org.neo4j.driver.internal.async.UnmanagedTransaction; @@ -79,7 +82,13 @@ public void initializeChannel( String userAgent, AuthToken authToken, RoutingContext routingContext, - ChannelPromise channelInitializedPromise) { + ChannelPromise channelInitializedPromise, + NotificationConfig notificationConfig) { + var exception = verifyNotificationConfigSupported(notificationConfig); + if (exception != null) { + channelInitializedPromise.setFailure(exception); + return; + } Channel channel = channelInitializedPromise.channel(); HelloMessage message; @@ -88,10 +97,15 @@ public void initializeChannel( userAgent, ((InternalAuthToken) authToken).toMap(), routingContext.toMap(), - includeDateTimeUtcPatchInHello()); + includeDateTimeUtcPatchInHello(), + notificationConfig); } else { message = new HelloMessage( - userAgent, ((InternalAuthToken) authToken).toMap(), null, includeDateTimeUtcPatchInHello()); + userAgent, + ((InternalAuthToken) authToken).toMap(), + null, + includeDateTimeUtcPatchInHello(), + notificationConfig); } HelloResponseHandler handler = new HelloResponseHandler(channelInitializedPromise); @@ -113,7 +127,15 @@ public void prepareToCloseChannel(Channel channel) { @Override public CompletionStage beginTransaction( - Connection connection, Set bookmarks, TransactionConfig config, String txType) { + Connection connection, + Set bookmarks, + TransactionConfig config, + String txType, + NotificationConfig notificationConfig) { + var exception = verifyNotificationConfigSupported(notificationConfig); + if (exception != null) { + return CompletableFuture.failedStage(exception); + } try { verifyDatabaseNameBeforeTransaction(connection.databaseName()); } catch (Exception error) { @@ -122,7 +144,13 @@ public CompletionStage beginTransaction( CompletableFuture beginTxFuture = new CompletableFuture<>(); BeginMessage beginMessage = new BeginMessage( - bookmarks, config, connection.databaseName(), connection.mode(), connection.impersonatedUser(), txType); + bookmarks, + config, + connection.databaseName(), + connection.mode(), + connection.impersonatedUser(), + txType, + notificationConfig); connection.writeAndFlush(beginMessage, new BeginTxResponseHandler(beginTxFuture)); return beginTxFuture; } @@ -148,10 +176,21 @@ public ResultCursorFactory runInAutoCommitTransaction( Set bookmarks, Consumer bookmarkConsumer, TransactionConfig config, - long fetchSize) { + long fetchSize, + NotificationConfig notificationConfig) { + var exception = verifyNotificationConfigSupported(notificationConfig); + if (exception != null) { + throw exception; + } verifyDatabaseNameBeforeTransaction(connection.databaseName()); RunWithMetadataMessage runMessage = autoCommitTxRunMessage( - query, config, connection.databaseName(), connection.mode(), bookmarks, connection.impersonatedUser()); + query, + config, + connection.databaseName(), + connection.mode(), + bookmarks, + connection.impersonatedUser(), + notificationConfig); return buildResultCursorFactory(connection, query, bookmarkConsumer, null, runMessage, fetchSize); } @@ -189,4 +228,14 @@ public BoltProtocolVersion version() { protected boolean includeDateTimeUtcPatchInHello() { return false; } + + protected Neo4jException verifyNotificationConfigSupported(NotificationConfig notificationConfig) { + Neo4jException exception = null; + if (notificationConfig != null && !notificationConfig.equals(NotificationConfig.defaultConfig())) { + exception = new UnsupportedFeatureException(String.format( + "Notification configuration is not supported on Bolt %s", + version().toString())); + } + return exception; + } } diff --git a/driver/src/main/java/org/neo4j/driver/internal/messaging/v51/BoltProtocolV51.java b/driver/src/main/java/org/neo4j/driver/internal/messaging/v51/BoltProtocolV51.java new file mode 100644 index 0000000000..a667f1247d --- /dev/null +++ b/driver/src/main/java/org/neo4j/driver/internal/messaging/v51/BoltProtocolV51.java @@ -0,0 +1,79 @@ +/* + * Copyright (c) "Neo4j" + * Neo4j Sweden AB [http://neo4j.com] + * + * This file is part of Neo4j. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.neo4j.driver.internal.messaging.v51; + +import static org.neo4j.driver.internal.async.connection.ChannelAttributes.messageDispatcher; + +import io.netty.channel.ChannelPromise; +import java.util.Collections; +import org.neo4j.driver.AuthToken; +import org.neo4j.driver.NotificationConfig; +import org.neo4j.driver.internal.cluster.RoutingContext; +import org.neo4j.driver.internal.handlers.HelloResponseHandler; +import org.neo4j.driver.internal.handlers.LogonResponseHandler; +import org.neo4j.driver.internal.messaging.BoltProtocol; +import org.neo4j.driver.internal.messaging.BoltProtocolVersion; +import org.neo4j.driver.internal.messaging.MessageFormat; +import org.neo4j.driver.internal.messaging.request.HelloMessage; +import org.neo4j.driver.internal.messaging.request.LogonMessage; +import org.neo4j.driver.internal.messaging.v5.BoltProtocolV5; +import org.neo4j.driver.internal.security.InternalAuthToken; + +public class BoltProtocolV51 extends BoltProtocolV5 { + public static final BoltProtocolVersion VERSION = new BoltProtocolVersion(5, 1); + public static final BoltProtocol INSTANCE = new BoltProtocolV51(); + + @Override + public void initializeChannel( + String userAgent, + AuthToken authToken, + RoutingContext routingContext, + ChannelPromise channelInitializedPromise, + NotificationConfig notificationConfig) { + var exception = verifyNotificationConfigSupported(notificationConfig); + if (exception != null) { + channelInitializedPromise.setFailure(exception); + return; + } + var channel = channelInitializedPromise.channel(); + HelloMessage message; + + if (routingContext.isServerRoutingEnabled()) { + message = new HelloMessage( + userAgent, Collections.emptyMap(), routingContext.toMap(), false, notificationConfig); + } else { + message = new HelloMessage(userAgent, Collections.emptyMap(), null, false, notificationConfig); + } + + messageDispatcher(channel).enqueue(new HelloResponseHandler(channel.voidPromise())); + messageDispatcher(channel).enqueue(new LogonResponseHandler(channelInitializedPromise)); + channel.write(message, channel.voidPromise()); + channel.writeAndFlush(new LogonMessage(((InternalAuthToken) authToken).toMap())); + } + + @Override + public BoltProtocolVersion version() { + return VERSION; + } + + @Override + public MessageFormat createMessageFormat() { + return new MessageFormatV51(); + } +} diff --git a/driver/src/main/java/org/neo4j/driver/internal/messaging/v51/MessageFormatV51.java b/driver/src/main/java/org/neo4j/driver/internal/messaging/v51/MessageFormatV51.java new file mode 100644 index 0000000000..96112e4f80 --- /dev/null +++ b/driver/src/main/java/org/neo4j/driver/internal/messaging/v51/MessageFormatV51.java @@ -0,0 +1,36 @@ +/* + * Copyright (c) "Neo4j" + * Neo4j Sweden AB [http://neo4j.com] + * + * This file is part of Neo4j. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.neo4j.driver.internal.messaging.v51; + +import org.neo4j.driver.internal.messaging.MessageFormat; +import org.neo4j.driver.internal.messaging.v5.MessageReaderV5; +import org.neo4j.driver.internal.packstream.PackInput; +import org.neo4j.driver.internal.packstream.PackOutput; + +public class MessageFormatV51 implements MessageFormat { + @Override + public Writer newWriter(PackOutput output) { + return new MessageWriterV51(output); + } + + @Override + public Reader newReader(PackInput input) { + return new MessageReaderV5(input); + } +} diff --git a/driver/src/main/java/org/neo4j/driver/internal/messaging/v51/MessageWriterV51.java b/driver/src/main/java/org/neo4j/driver/internal/messaging/v51/MessageWriterV51.java new file mode 100644 index 0000000000..d36f8af518 --- /dev/null +++ b/driver/src/main/java/org/neo4j/driver/internal/messaging/v51/MessageWriterV51.java @@ -0,0 +1,73 @@ +/* + * Copyright (c) "Neo4j" + * Neo4j Sweden AB [http://neo4j.com] + * + * This file is part of Neo4j. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.neo4j.driver.internal.messaging.v51; + +import java.util.Map; +import org.neo4j.driver.internal.messaging.AbstractMessageWriter; +import org.neo4j.driver.internal.messaging.MessageEncoder; +import org.neo4j.driver.internal.messaging.common.CommonValuePacker; +import org.neo4j.driver.internal.messaging.encode.BeginMessageEncoder; +import org.neo4j.driver.internal.messaging.encode.CommitMessageEncoder; +import org.neo4j.driver.internal.messaging.encode.DiscardMessageEncoder; +import org.neo4j.driver.internal.messaging.encode.GoodbyeMessageEncoder; +import org.neo4j.driver.internal.messaging.encode.HelloMessageEncoder; +import org.neo4j.driver.internal.messaging.encode.LogonMessageEncoder; +import org.neo4j.driver.internal.messaging.encode.PullMessageEncoder; +import org.neo4j.driver.internal.messaging.encode.ResetMessageEncoder; +import org.neo4j.driver.internal.messaging.encode.RollbackMessageEncoder; +import org.neo4j.driver.internal.messaging.encode.RouteV44MessageEncoder; +import org.neo4j.driver.internal.messaging.encode.RunWithMetadataMessageEncoder; +import org.neo4j.driver.internal.messaging.request.BeginMessage; +import org.neo4j.driver.internal.messaging.request.CommitMessage; +import org.neo4j.driver.internal.messaging.request.DiscardMessage; +import org.neo4j.driver.internal.messaging.request.GoodbyeMessage; +import org.neo4j.driver.internal.messaging.request.HelloMessage; +import org.neo4j.driver.internal.messaging.request.LogonMessage; +import org.neo4j.driver.internal.messaging.request.PullMessage; +import org.neo4j.driver.internal.messaging.request.ResetMessage; +import org.neo4j.driver.internal.messaging.request.RollbackMessage; +import org.neo4j.driver.internal.messaging.request.RouteMessage; +import org.neo4j.driver.internal.messaging.request.RunWithMetadataMessage; +import org.neo4j.driver.internal.packstream.PackOutput; +import org.neo4j.driver.internal.util.Iterables; + +public class MessageWriterV51 extends AbstractMessageWriter { + public MessageWriterV51(PackOutput output) { + super(new CommonValuePacker(output, true), buildEncoders()); + } + + private static Map buildEncoders() { + Map result = Iterables.newHashMapWithSize(9); + result.put(HelloMessage.SIGNATURE, new HelloMessageEncoder()); + result.put(LogonMessage.SIGNATURE, new LogonMessageEncoder()); + result.put(GoodbyeMessage.SIGNATURE, new GoodbyeMessageEncoder()); + result.put(RunWithMetadataMessage.SIGNATURE, new RunWithMetadataMessageEncoder()); + result.put(RouteMessage.SIGNATURE, new RouteV44MessageEncoder()); + + result.put(DiscardMessage.SIGNATURE, new DiscardMessageEncoder()); + result.put(PullMessage.SIGNATURE, new PullMessageEncoder()); + + result.put(BeginMessage.SIGNATURE, new BeginMessageEncoder()); + result.put(CommitMessage.SIGNATURE, new CommitMessageEncoder()); + result.put(RollbackMessage.SIGNATURE, new RollbackMessageEncoder()); + + result.put(ResetMessage.SIGNATURE, new ResetMessageEncoder()); + return result; + } +} diff --git a/driver/src/main/java/org/neo4j/driver/internal/messaging/v52/BoltProtocolV52.java b/driver/src/main/java/org/neo4j/driver/internal/messaging/v52/BoltProtocolV52.java new file mode 100644 index 0000000000..11b47f5d18 --- /dev/null +++ b/driver/src/main/java/org/neo4j/driver/internal/messaging/v52/BoltProtocolV52.java @@ -0,0 +1,74 @@ +/* + * Copyright (c) "Neo4j" + * Neo4j Sweden AB [http://neo4j.com] + * + * This file is part of Neo4j. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.neo4j.driver.internal.messaging.v52; + +import static org.neo4j.driver.internal.async.connection.ChannelAttributes.messageDispatcher; + +import io.netty.channel.ChannelPromise; +import java.util.Collections; +import org.neo4j.driver.AuthToken; +import org.neo4j.driver.NotificationConfig; +import org.neo4j.driver.exceptions.Neo4jException; +import org.neo4j.driver.internal.cluster.RoutingContext; +import org.neo4j.driver.internal.handlers.HelloResponseHandler; +import org.neo4j.driver.internal.handlers.LogonResponseHandler; +import org.neo4j.driver.internal.messaging.BoltProtocol; +import org.neo4j.driver.internal.messaging.BoltProtocolVersion; +import org.neo4j.driver.internal.messaging.request.HelloMessage; +import org.neo4j.driver.internal.messaging.request.LogonMessage; +import org.neo4j.driver.internal.messaging.v51.BoltProtocolV51; +import org.neo4j.driver.internal.security.InternalAuthToken; + +public class BoltProtocolV52 extends BoltProtocolV51 { + public static final BoltProtocolVersion VERSION = new BoltProtocolVersion(5, 2); + public static final BoltProtocol INSTANCE = new BoltProtocolV52(); + + @Override + public void initializeChannel( + String userAgent, + AuthToken authToken, + RoutingContext routingContext, + ChannelPromise channelInitializedPromise, + NotificationConfig notificationConfig) { + var channel = channelInitializedPromise.channel(); + HelloMessage message; + + if (routingContext.isServerRoutingEnabled()) { + message = new HelloMessage( + userAgent, Collections.emptyMap(), routingContext.toMap(), false, notificationConfig); + } else { + message = new HelloMessage(userAgent, Collections.emptyMap(), null, false, notificationConfig); + } + + messageDispatcher(channel).enqueue(new HelloResponseHandler(channel.voidPromise())); + messageDispatcher(channel).enqueue(new LogonResponseHandler(channelInitializedPromise)); + channel.write(message, channel.voidPromise()); + channel.writeAndFlush(new LogonMessage(((InternalAuthToken) authToken).toMap())); + } + + @Override + protected Neo4jException verifyNotificationConfigSupported(NotificationConfig notificationConfig) { + return null; + } + + @Override + public BoltProtocolVersion version() { + return VERSION; + } +} diff --git a/driver/src/main/java/org/neo4j/driver/internal/summary/InternalNotification.java b/driver/src/main/java/org/neo4j/driver/internal/summary/InternalNotification.java index a1f9082eef..17feb6e70d 100644 --- a/driver/src/main/java/org/neo4j/driver/internal/summary/InternalNotification.java +++ b/driver/src/main/java/org/neo4j/driver/internal/summary/InternalNotification.java @@ -20,46 +20,66 @@ import static org.neo4j.driver.internal.value.NullValue.NULL; +import java.util.Optional; import java.util.function.Function; +import org.neo4j.driver.NotificationCategory; +import org.neo4j.driver.NotificationSeverity; import org.neo4j.driver.Value; +import org.neo4j.driver.internal.InternalNotificationCategory; +import org.neo4j.driver.internal.InternalNotificationSeverity; import org.neo4j.driver.summary.InputPosition; import org.neo4j.driver.summary.Notification; public class InternalNotification implements Notification { - public static final Function VALUE_TO_NOTIFICATION = new Function() { - @Override - public Notification apply(Value value) { - String code = value.get("code").asString(); - String title = value.get("title").asString(); - String description = value.get("description").asString(); - String severity = - value.containsKey("severity") ? value.get("severity").asString() : "N/A"; - - Value posValue = value.get("position"); - InputPosition position = null; - if (posValue != NULL) { - position = new InternalInputPosition( - posValue.get("offset").asInt(), - posValue.get("line").asInt(), - posValue.get("column").asInt()); - } - - return new InternalNotification(code, title, description, severity, position); + public static final Function VALUE_TO_NOTIFICATION = value -> { + String code = value.get("code").asString(); + String title = value.get("title").asString(); + String description = value.get("description").asString(); + var rawSeverityLevel = + value.containsKey("severity") ? value.get("severity").asString() : null; + var severityLevel = + InternalNotificationSeverity.valueOf(rawSeverityLevel).orElse(null); + var rawCategory = value.containsKey("category") ? value.get("category").asString() : null; + var category = InternalNotificationCategory.valueOf(rawCategory).orElse(null); + + Value posValue = value.get("position"); + InputPosition position = null; + if (posValue != NULL) { + position = new InternalInputPosition( + posValue.get("offset").asInt(), + posValue.get("line").asInt(), + posValue.get("column").asInt()); } + + return new InternalNotification( + code, title, description, severityLevel, rawSeverityLevel, category, rawCategory, position); }; private final String code; private final String title; private final String description; - private final String severity; + private final NotificationSeverity severityLevel; + private final String rawSeverityLevel; + private final NotificationCategory category; + private final String rawCategory; private final InputPosition position; public InternalNotification( - String code, String title, String description, String severity, InputPosition position) { + String code, + String title, + String description, + NotificationSeverity severityLevel, + String rawSeverityLevel, + NotificationCategory category, + String rawCategory, + InputPosition position) { this.code = code; this.title = title; this.description = description; - this.severity = severity; + this.severityLevel = severityLevel; + this.rawSeverityLevel = rawSeverityLevel; + this.category = category; + this.rawCategory = rawCategory; this.position = position; } @@ -84,13 +104,30 @@ public InputPosition position() { } @Override - public String severity() { - return severity; + public Optional severityLevel() { + return Optional.ofNullable(severityLevel); + } + + @Override + public Optional rawSeverityLevel() { + return Optional.ofNullable(rawSeverityLevel); + } + + @Override + public Optional category() { + return Optional.ofNullable(category); + } + + @Override + public Optional rawCategory() { + return Optional.ofNullable(rawCategory); } @Override public String toString() { - String info = "code=" + code + ", title=" + title + ", description=" + description + ", severity=" + severity; + var info = "code=" + code + ", title=" + title + ", description=" + description + ", severityLevel=" + + severityLevel + ", rawSeverityLevel=" + rawSeverityLevel + ", category=" + category + ", rawCategory=" + + rawCategory; return position == null ? info : info + ", position={" + position + "}"; } } diff --git a/driver/src/main/java/org/neo4j/driver/summary/Notification.java b/driver/src/main/java/org/neo4j/driver/summary/Notification.java index df12784bb5..cba26c8489 100644 --- a/driver/src/main/java/org/neo4j/driver/summary/Notification.java +++ b/driver/src/main/java/org/neo4j/driver/summary/Notification.java @@ -18,11 +18,14 @@ */ package org.neo4j.driver.summary; +import java.util.Optional; +import org.neo4j.driver.NotificationCategory; +import org.neo4j.driver.NotificationSeverity; import org.neo4j.driver.util.Immutable; /** * Representation for notifications found when executing a query. - * + *

* A notification can be visualized in a client pinpointing problems or other information about the query. * @since 1.0 */ @@ -58,7 +61,51 @@ public interface Notification { /** * The severity level of the notification. * + * @deprecated superseded by {@link #severityLevel()} and {@link #rawSeverityLevel()} + * @return the severity level of the notification + */ + @Deprecated + default String severity() { + return rawSeverityLevel().orElse("N/A"); + } + + /** + * Returns the severity level of the notification. + * * @return the severity level of the notification + * @since 5.7 + */ + default Optional severityLevel() { + return Optional.empty(); + } + + /** + * Returns the raw severity level of the notification as a String returned by the server. + * + * @return the severity level of the notification + * @since 5.7 + */ + default Optional rawSeverityLevel() { + return Optional.empty(); + } + + /** + * Returns the category of the notification. + * + * @return the category of the notification + * @since 5.7 + */ + default Optional category() { + return Optional.empty(); + } + + /** + * Returns the raw category of the notification as a String returned by the server. + * + * @return the category of the notification + * @since 5.7 */ - String severity(); + default Optional rawCategory() { + return Optional.empty(); + } } diff --git a/driver/src/test/java/org/neo4j/driver/ConfigTest.java b/driver/src/test/java/org/neo4j/driver/ConfigTest.java index 098854f655..c105cce4c5 100644 --- a/driver/src/test/java/org/neo4j/driver/ConfigTest.java +++ b/driver/src/test/java/org/neo4j/driver/ConfigTest.java @@ -35,6 +35,7 @@ import java.io.Serializable; import java.lang.reflect.Field; import java.util.List; +import java.util.Set; import java.util.concurrent.TimeUnit; import java.util.logging.Level; import org.junit.jupiter.api.Nested; @@ -463,6 +464,10 @@ void shouldSerialize() throws Exception { .withRoutingTablePurgeDelay(50000, TimeUnit.MILLISECONDS) .withLeakedSessionsLogging() .withMetricsAdapter(MetricsAdapter.MICROMETER) + .withNotificationConfig(NotificationConfig.defaultConfig() + .enableMinimumSeverity(NotificationSeverity.WARNING) + .disableCategories( + Set.of(NotificationCategory.UNSUPPORTED, NotificationCategory.UNRECOGNIZED))) .build(); Config verify = TestUtil.serializeAndReadBack(config, Config.class); @@ -492,6 +497,12 @@ void shouldSerialize() throws Exception { assertEquals(config.metricsAdapter(), verify.metricsAdapter()); assertEquals(config.maxTransactionRetryTimeMillis(), verify.maxTransactionRetryTimeMillis()); assertEquals(config.logLeakedSessions(), verify.logLeakedSessions()); + assertEquals( + NotificationConfig.defaultConfig() + .enableMinimumSeverity(NotificationSeverity.WARNING) + .disableCategories( + Set.of(NotificationCategory.UNSUPPORTED, NotificationCategory.UNRECOGNIZED)), + config.notificationConfig()); } @Test diff --git a/driver/src/test/java/org/neo4j/driver/ParametersTest.java b/driver/src/test/java/org/neo4j/driver/ParametersTest.java index 9f8344670c..15de822025 100644 --- a/driver/src/test/java/org/neo4j/driver/ParametersTest.java +++ b/driver/src/test/java/org/neo4j/driver/ParametersTest.java @@ -111,7 +111,8 @@ private Session mockedSession() { null, UNLIMITED_FETCH_SIZE, DEV_NULL_LOGGING, - mock(BookmarkManager.class)); + mock(BookmarkManager.class), + null); return new InternalSession(session); } } diff --git a/driver/src/test/java/org/neo4j/driver/SessionConfigTest.java b/driver/src/test/java/org/neo4j/driver/SessionConfigTest.java index 69205f00a4..687a72cbff 100644 --- a/driver/src/test/java/org/neo4j/driver/SessionConfigTest.java +++ b/driver/src/test/java/org/neo4j/driver/SessionConfigTest.java @@ -184,6 +184,9 @@ void shouldSerialize() throws Exception { .withFetchSize(54321L) .withDatabase("testing") .withImpersonatedUser("impersonator") + .withNotificationConfig(NotificationConfig.defaultConfig() + .enableMinimumSeverity(NotificationSeverity.WARNING) + .disableCategories(Set.of(NotificationCategory.UNSUPPORTED, NotificationCategory.UNRECOGNIZED))) .build(); SessionConfig verify = TestUtil.serializeAndReadBack(config, SessionConfig.class); @@ -200,5 +203,10 @@ void shouldSerialize() throws Exception { assertEquals(config.fetchSize(), verify.fetchSize()); assertEquals(config.database(), verify.database()); assertEquals(config.impersonatedUser(), verify.impersonatedUser()); + assertEquals( + NotificationConfig.defaultConfig() + .enableMinimumSeverity(NotificationSeverity.WARNING) + .disableCategories(Set.of(NotificationCategory.UNSUPPORTED, NotificationCategory.UNRECOGNIZED)), + config.notificationConfig()); } } diff --git a/driver/src/test/java/org/neo4j/driver/integration/ChannelConnectorImplIT.java b/driver/src/test/java/org/neo4j/driver/integration/ChannelConnectorImplIT.java index e99e963a84..6073114a30 100644 --- a/driver/src/test/java/org/neo4j/driver/integration/ChannelConnectorImplIT.java +++ b/driver/src/test/java/org/neo4j/driver/integration/ChannelConnectorImplIT.java @@ -218,7 +218,8 @@ private ChannelConnectorImpl newConnector( DEV_NULL_LOGGING, new FakeClock(), RoutingContext.EMPTY, - DefaultDomainNameResolver.getInstance()); + DefaultDomainNameResolver.getInstance(), + null); } private static SecurityPlan trustAllCertificates() throws GeneralSecurityException { diff --git a/driver/src/test/java/org/neo4j/driver/integration/SummaryIT.java b/driver/src/test/java/org/neo4j/driver/integration/SummaryIT.java index e10bf49f90..4fce571661 100644 --- a/driver/src/test/java/org/neo4j/driver/integration/SummaryIT.java +++ b/driver/src/test/java/org/neo4j/driver/integration/SummaryIT.java @@ -236,6 +236,7 @@ void shouldContainProfile() { } @Test + @SuppressWarnings("deprecation") void shouldContainNotifications() { // When ResultSummary summary = @@ -250,6 +251,8 @@ void shouldContainNotifications() { assertThat(notification.title(), notNullValue()); assertThat(notification.description(), notNullValue()); assertThat(notification.severity(), notNullValue()); + assertThat(notification.severityLevel(), notNullValue()); + assertThat(notification.rawSeverityLevel(), notNullValue()); assertThat(notification.position(), notNullValue()); } diff --git a/driver/src/test/java/org/neo4j/driver/internal/InternalNotificationSeverityTest.java b/driver/src/test/java/org/neo4j/driver/internal/InternalNotificationSeverityTest.java new file mode 100644 index 0000000000..2c6862d8c7 --- /dev/null +++ b/driver/src/test/java/org/neo4j/driver/internal/InternalNotificationSeverityTest.java @@ -0,0 +1,46 @@ +/* + * Copyright (c) "Neo4j" + * Neo4j Sweden AB [http://neo4j.com] + * + * This file is part of Neo4j. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.neo4j.driver.internal; + +import static org.junit.jupiter.api.Assertions.assertEquals; + +import java.util.Collections; +import java.util.List; +import org.junit.jupiter.api.Test; +import org.neo4j.driver.NotificationSeverity; + +class InternalNotificationSeverityTest { + @Test + void shouldSortByLevel() { + // GIVEN + var list = new java.util.ArrayList<>(List.of( + NotificationSeverity.WARNING, InternalNotificationSeverity.OFF, NotificationSeverity.INFORMATION)); + + // WHEN + Collections.sort(list); + + // THEN + assertEquals( + List.of( + NotificationSeverity.INFORMATION, + NotificationSeverity.WARNING, + InternalNotificationSeverity.OFF), + list); + } +} 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 a6b1379fe9..1606047906 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 @@ -101,7 +101,8 @@ private static LeakLoggingNetworkSession newSession(Logging logging, boolean ope null, FetchSizeUtil.UNLIMITED_FETCH_SIZE, logging, - mock(BookmarkManager.class)); + mock(BookmarkManager.class), + null); } private static ConnectionProvider connectionProviderMock(boolean openConnection) { 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 3750126bd2..06bcfcc393 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 @@ -172,7 +172,7 @@ void shouldBeClosedWhenMarkedTerminatedAndClosed() { void shouldReleaseConnectionWhenBeginFails() { RuntimeException error = new RuntimeException("Wrong bookmark!"); Connection connection = connectionWithBegin(handler -> handler.onFailure(error)); - UnmanagedTransaction tx = new UnmanagedTransaction(connection, (ignored) -> {}, UNLIMITED_FETCH_SIZE); + UnmanagedTransaction tx = new UnmanagedTransaction(connection, (ignored) -> {}, UNLIMITED_FETCH_SIZE, null); Set bookmarks = Collections.singleton(InternalBookmark.parse("SomeBookmark")); TransactionConfig txConfig = TransactionConfig.empty(); @@ -187,7 +187,7 @@ void shouldReleaseConnectionWhenBeginFails() { @Test void shouldNotReleaseConnectionWhenBeginSucceeds() { Connection connection = connectionWithBegin(handler -> handler.onSuccess(emptyMap())); - UnmanagedTransaction tx = new UnmanagedTransaction(connection, (ignored) -> {}, UNLIMITED_FETCH_SIZE); + UnmanagedTransaction tx = new UnmanagedTransaction(connection, (ignored) -> {}, UNLIMITED_FETCH_SIZE, null); Set bookmarks = Collections.singleton(InternalBookmark.parse("SomeBookmark")); TransactionConfig txConfig = TransactionConfig.empty(); @@ -200,7 +200,7 @@ void shouldNotReleaseConnectionWhenBeginSucceeds() { @Test void shouldReleaseConnectionWhenTerminatedAndCommitted() { Connection connection = connectionMock(); - UnmanagedTransaction tx = new UnmanagedTransaction(connection, (ignored) -> {}, UNLIMITED_FETCH_SIZE); + UnmanagedTransaction tx = new UnmanagedTransaction(connection, (ignored) -> {}, UNLIMITED_FETCH_SIZE, null); tx.markTerminated(null); @@ -216,7 +216,7 @@ void shouldNotCreateCircularExceptionWhenTerminationCauseEqualsToCursorFailure() ClientException terminationCause = new ClientException("Custom exception"); ResultCursorsHolder resultCursorsHolder = mockResultCursorWith(terminationCause); UnmanagedTransaction tx = - new UnmanagedTransaction(connection, (ignored) -> {}, UNLIMITED_FETCH_SIZE, resultCursorsHolder); + new UnmanagedTransaction(connection, (ignored) -> {}, UNLIMITED_FETCH_SIZE, resultCursorsHolder, null); tx.markTerminated(terminationCause); @@ -231,7 +231,7 @@ void shouldNotCreateCircularExceptionWhenTerminationCauseDifferentFromCursorFail ClientException terminationCause = new ClientException("Custom exception"); ResultCursorsHolder resultCursorsHolder = mockResultCursorWith(new ClientException("Cursor error")); UnmanagedTransaction tx = - new UnmanagedTransaction(connection, (ignored) -> {}, UNLIMITED_FETCH_SIZE, resultCursorsHolder); + new UnmanagedTransaction(connection, (ignored) -> {}, UNLIMITED_FETCH_SIZE, resultCursorsHolder, null); tx.markTerminated(terminationCause); @@ -247,7 +247,7 @@ void shouldNotCreateCircularExceptionWhenTerminationCauseDifferentFromCursorFail void shouldNotCreateCircularExceptionWhenTerminatedWithoutFailure() { Connection connection = connectionMock(); ClientException terminationCause = new ClientException("Custom exception"); - UnmanagedTransaction tx = new UnmanagedTransaction(connection, (ignored) -> {}, UNLIMITED_FETCH_SIZE); + UnmanagedTransaction tx = new UnmanagedTransaction(connection, (ignored) -> {}, UNLIMITED_FETCH_SIZE, null); tx.markTerminated(terminationCause); @@ -260,7 +260,7 @@ void shouldNotCreateCircularExceptionWhenTerminatedWithoutFailure() { @Test void shouldReleaseConnectionWhenTerminatedAndRolledBack() { Connection connection = connectionMock(); - UnmanagedTransaction tx = new UnmanagedTransaction(connection, (ignored) -> {}, UNLIMITED_FETCH_SIZE); + UnmanagedTransaction tx = new UnmanagedTransaction(connection, (ignored) -> {}, UNLIMITED_FETCH_SIZE, null); tx.markTerminated(null); await(tx.rollbackAsync()); @@ -271,7 +271,7 @@ void shouldReleaseConnectionWhenTerminatedAndRolledBack() { @Test void shouldReleaseConnectionWhenClose() throws Throwable { Connection connection = connectionMock(); - UnmanagedTransaction tx = new UnmanagedTransaction(connection, (ignored) -> {}, UNLIMITED_FETCH_SIZE); + UnmanagedTransaction tx = new UnmanagedTransaction(connection, (ignored) -> {}, UNLIMITED_FETCH_SIZE, null); await(tx.closeAsync()); @@ -282,7 +282,7 @@ void shouldReleaseConnectionWhenClose() throws Throwable { void shouldReleaseConnectionOnConnectionAuthorizationExpiredExceptionFailure() { AuthorizationExpiredException exception = new AuthorizationExpiredException("code", "message"); Connection connection = connectionWithBegin(handler -> handler.onFailure(exception)); - UnmanagedTransaction tx = new UnmanagedTransaction(connection, (ignored) -> {}, UNLIMITED_FETCH_SIZE); + UnmanagedTransaction tx = new UnmanagedTransaction(connection, (ignored) -> {}, UNLIMITED_FETCH_SIZE, null); Set bookmarks = Collections.singleton(InternalBookmark.parse("SomeBookmark")); TransactionConfig txConfig = TransactionConfig.empty(); @@ -298,7 +298,7 @@ void shouldReleaseConnectionOnConnectionAuthorizationExpiredExceptionFailure() { void shouldReleaseConnectionOnConnectionReadTimeoutExceptionFailure() { Connection connection = connectionWithBegin(handler -> handler.onFailure(ConnectionReadTimeoutException.INSTANCE)); - UnmanagedTransaction tx = new UnmanagedTransaction(connection, (ignored) -> {}, UNLIMITED_FETCH_SIZE); + UnmanagedTransaction tx = new UnmanagedTransaction(connection, (ignored) -> {}, UNLIMITED_FETCH_SIZE, null); Set bookmarks = Collections.singleton(InternalBookmark.parse("SomeBookmark")); TransactionConfig txConfig = TransactionConfig.empty(); @@ -328,7 +328,7 @@ void shouldReturnExistingStageOnSimilarCompletingAction( given(connection.protocol()).willReturn(protocol); given(protocolCommit ? protocol.commitTransaction(connection) : protocol.rollbackTransaction(connection)) .willReturn(new CompletableFuture<>()); - UnmanagedTransaction tx = new UnmanagedTransaction(connection, (ignored) -> {}, UNLIMITED_FETCH_SIZE); + UnmanagedTransaction tx = new UnmanagedTransaction(connection, (ignored) -> {}, UNLIMITED_FETCH_SIZE, null); CompletionStage initialStage = mapTransactionAction(initialAction, tx).get(); @@ -370,7 +370,7 @@ void shouldReturnFailingStageOnConflictingCompletingAction( given(connection.protocol()).willReturn(protocol); given(protocolCommit ? protocol.commitTransaction(connection) : protocol.rollbackTransaction(connection)) .willReturn(protocolActionCompleted ? completedFuture(null) : new CompletableFuture<>()); - UnmanagedTransaction tx = new UnmanagedTransaction(connection, (ignored) -> {}, UNLIMITED_FETCH_SIZE); + UnmanagedTransaction tx = new UnmanagedTransaction(connection, (ignored) -> {}, UNLIMITED_FETCH_SIZE, null); CompletionStage originalActionStage = mapTransactionAction(initialAction, tx).get(); @@ -413,7 +413,7 @@ void shouldReturnCompletedWithNullStageOnClosingInactiveTransactionExceptCommitt given(connection.protocol()).willReturn(protocol); given(protocolCommit ? protocol.commitTransaction(connection) : protocol.rollbackTransaction(connection)) .willReturn(completedFuture(null)); - UnmanagedTransaction tx = new UnmanagedTransaction(connection, (ignored) -> {}, UNLIMITED_FETCH_SIZE); + UnmanagedTransaction tx = new UnmanagedTransaction(connection, (ignored) -> {}, UNLIMITED_FETCH_SIZE, null); CompletionStage originalActionStage = mapTransactionAction(originalAction, tx).get(); @@ -461,7 +461,7 @@ private static UnmanagedTransaction beginTx(Connection connection) { } private static UnmanagedTransaction beginTx(Connection connection, Set initialBookmarks) { - UnmanagedTransaction tx = new UnmanagedTransaction(connection, (ignored) -> {}, UNLIMITED_FETCH_SIZE); + UnmanagedTransaction tx = new UnmanagedTransaction(connection, (ignored) -> {}, UNLIMITED_FETCH_SIZE, null); return await(tx.beginAsync(initialBookmarks, TransactionConfig.empty(), null)); } diff --git a/driver/src/test/java/org/neo4j/driver/internal/async/connection/BoltProtocolUtilTest.java b/driver/src/test/java/org/neo4j/driver/internal/async/connection/BoltProtocolUtilTest.java index a9dedc3676..3736d7d743 100644 --- a/driver/src/test/java/org/neo4j/driver/internal/async/connection/BoltProtocolUtilTest.java +++ b/driver/src/test/java/org/neo4j/driver/internal/async/connection/BoltProtocolUtilTest.java @@ -33,7 +33,7 @@ import org.neo4j.driver.internal.messaging.v3.BoltProtocolV3; import org.neo4j.driver.internal.messaging.v41.BoltProtocolV41; import org.neo4j.driver.internal.messaging.v44.BoltProtocolV44; -import org.neo4j.driver.internal.messaging.v5.BoltProtocolV5; +import org.neo4j.driver.internal.messaging.v52.BoltProtocolV52; class BoltProtocolUtilTest { @Test @@ -41,7 +41,7 @@ void shouldReturnHandshakeBuf() { assertByteBufContains( handshakeBuf(), BOLT_MAGIC_PREAMBLE, - BoltProtocolV5.VERSION.toInt(), + (2 << 16) | BoltProtocolV52.VERSION.toInt(), (2 << 16) | BoltProtocolV44.VERSION.toInt(), BoltProtocolV41.VERSION.toInt(), BoltProtocolV3.VERSION.toInt()); @@ -49,7 +49,7 @@ void shouldReturnHandshakeBuf() { @Test void shouldReturnHandshakeString() { - assertEquals("[0x6060b017, 5, 132100, 260, 3]", handshakeString()); + assertEquals("[0x6060b017, 131589, 132100, 260, 3]", handshakeString()); } @Test diff --git a/driver/src/test/java/org/neo4j/driver/internal/async/connection/HandshakeCompletedListenerTest.java b/driver/src/test/java/org/neo4j/driver/internal/async/connection/HandshakeCompletedListenerTest.java index 01775ec7cd..934b7c96c9 100644 --- a/driver/src/test/java/org/neo4j/driver/internal/async/connection/HandshakeCompletedListenerTest.java +++ b/driver/src/test/java/org/neo4j/driver/internal/async/connection/HandshakeCompletedListenerTest.java @@ -59,7 +59,7 @@ void tearDown() { void shouldFailConnectionInitializedPromiseWhenHandshakeFails() { ChannelPromise channelInitializedPromise = channel.newPromise(); HandshakeCompletedListener listener = new HandshakeCompletedListener( - "user-agent", authToken(), RoutingContext.EMPTY, channelInitializedPromise); + "user-agent", authToken(), RoutingContext.EMPTY, channelInitializedPromise, null); ChannelPromise handshakeCompletedPromise = channel.newPromise(); IOException cause = new IOException("Bad handshake"); @@ -75,7 +75,7 @@ void shouldFailConnectionInitializedPromiseWhenHandshakeFails() { void shouldWriteInitializationMessageInBoltV3WhenHandshakeCompleted() { testWritingOfInitializationMessage( BoltProtocolV3.VERSION, - new HelloMessage(USER_AGENT, authToken().toMap(), Collections.emptyMap(), false), + new HelloMessage(USER_AGENT, authToken().toMap(), Collections.emptyMap(), false, null), HelloResponseHandler.class); } @@ -89,7 +89,7 @@ private void testWritingOfInitializationMessage( ChannelPromise channelInitializedPromise = channel.newPromise(); HandshakeCompletedListener listener = new HandshakeCompletedListener( - USER_AGENT, authToken(), RoutingContext.EMPTY, channelInitializedPromise); + USER_AGENT, authToken(), RoutingContext.EMPTY, channelInitializedPromise, null); ChannelPromise handshakeCompletedPromise = channel.newPromise(); handshakeCompletedPromise.setSuccess(); diff --git a/driver/src/test/java/org/neo4j/driver/internal/async/pool/ConnectionPoolImplIT.java b/driver/src/test/java/org/neo4j/driver/internal/async/pool/ConnectionPoolImplIT.java index 139781d742..09e8173bf1 100644 --- a/driver/src/test/java/org/neo4j/driver/internal/async/pool/ConnectionPoolImplIT.java +++ b/driver/src/test/java/org/neo4j/driver/internal/async/pool/ConnectionPoolImplIT.java @@ -141,7 +141,8 @@ private ConnectionPoolImpl newPool() throws Exception { DEV_NULL_LOGGING, clock, RoutingContext.EMPTY, - DefaultDomainNameResolver.getInstance()); + DefaultDomainNameResolver.getInstance(), + null); PoolSettings poolSettings = newSettings(); Bootstrap bootstrap = BootstrapFactory.newBootstrap(1); return new ConnectionPoolImpl( diff --git a/driver/src/test/java/org/neo4j/driver/internal/async/pool/NettyChannelPoolIT.java b/driver/src/test/java/org/neo4j/driver/internal/async/pool/NettyChannelPoolIT.java index e9f66227a5..2f83714237 100644 --- a/driver/src/test/java/org/neo4j/driver/internal/async/pool/NettyChannelPoolIT.java +++ b/driver/src/test/java/org/neo4j/driver/internal/async/pool/NettyChannelPoolIT.java @@ -174,7 +174,8 @@ private NettyChannelPool newPool(AuthToken authToken, int maxConnections) { DEV_NULL_LOGGING, new FakeClock(), RoutingContext.EMPTY, - DefaultDomainNameResolver.getInstance()); + DefaultDomainNameResolver.getInstance(), + null); return new NettyChannelPool( neo4j.address(), connector, bootstrap, poolHandler, ChannelHealthChecker.ACTIVE, 1_000, maxConnections); } diff --git a/driver/src/test/java/org/neo4j/driver/internal/messaging/encode/BeginMessageEncoderTest.java b/driver/src/test/java/org/neo4j/driver/internal/messaging/encode/BeginMessageEncoderTest.java index 490fa16d64..e216918610 100644 --- a/driver/src/test/java/org/neo4j/driver/internal/messaging/encode/BeginMessageEncoderTest.java +++ b/driver/src/test/java/org/neo4j/driver/internal/messaging/encode/BeginMessageEncoderTest.java @@ -62,7 +62,8 @@ void shouldEncodeBeginMessage(AccessMode mode, String impersonatedUser, String t Duration txTimeout = Duration.ofSeconds(1); encoder.encode( - new BeginMessage(bookmarks, txTimeout, txMetadata, mode, defaultDatabase(), impersonatedUser, txType), + new BeginMessage( + bookmarks, txTimeout, txMetadata, mode, defaultDatabase(), impersonatedUser, txType, null), packer); InOrder order = inOrder(packer); diff --git a/driver/src/test/java/org/neo4j/driver/internal/messaging/encode/HelloMessageEncoderTest.java b/driver/src/test/java/org/neo4j/driver/internal/messaging/encode/HelloMessageEncoderTest.java index 2651f62e5b..aa2c169492 100644 --- a/driver/src/test/java/org/neo4j/driver/internal/messaging/encode/HelloMessageEncoderTest.java +++ b/driver/src/test/java/org/neo4j/driver/internal/messaging/encode/HelloMessageEncoderTest.java @@ -42,7 +42,7 @@ void shouldEncodeHelloMessage() throws Exception { authToken.put("username", value("bob")); authToken.put("password", value("secret")); - encoder.encode(new HelloMessage("MyDriver", authToken, null, false), packer); + encoder.encode(new HelloMessage("MyDriver", authToken, null, false, null), packer); InOrder order = inOrder(packer); order.verify(packer).packStructHeader(1, HelloMessage.SIGNATURE); @@ -61,7 +61,7 @@ void shouldEncodeHelloMessageWithRoutingContext() throws Exception { Map routingContext = new HashMap<>(); routingContext.put("policy", "eu-fast"); - encoder.encode(new HelloMessage("MyDriver", authToken, routingContext, false), packer); + encoder.encode(new HelloMessage("MyDriver", authToken, routingContext, false, null), packer); InOrder order = inOrder(packer); order.verify(packer).packStructHeader(1, HelloMessage.SIGNATURE); diff --git a/driver/src/test/java/org/neo4j/driver/internal/messaging/encode/RunWithMetadataMessageEncoderTest.java b/driver/src/test/java/org/neo4j/driver/internal/messaging/encode/RunWithMetadataMessageEncoderTest.java index 76629dfc22..f200e4cfbc 100644 --- a/driver/src/test/java/org/neo4j/driver/internal/messaging/encode/RunWithMetadataMessageEncoderTest.java +++ b/driver/src/test/java/org/neo4j/driver/internal/messaging/encode/RunWithMetadataMessageEncoderTest.java @@ -66,7 +66,8 @@ void shouldEncodeRunWithMetadataMessage(AccessMode mode) throws Exception { Query query = new Query("RETURN $answer", value(params)); encoder.encode( - autoCommitTxRunMessage(query, txTimeout, txMetadata, defaultDatabase(), mode, bookmarks, null), packer); + autoCommitTxRunMessage(query, txTimeout, txMetadata, defaultDatabase(), mode, bookmarks, null, null), + packer); InOrder order = inOrder(packer); order.verify(packer).packStructHeader(3, RunWithMetadataMessage.SIGNATURE); diff --git a/driver/src/test/java/org/neo4j/driver/internal/messaging/request/HelloMessageTest.java b/driver/src/test/java/org/neo4j/driver/internal/messaging/request/HelloMessageTest.java index bfb1dae179..f099aad8c4 100644 --- a/driver/src/test/java/org/neo4j/driver/internal/messaging/request/HelloMessageTest.java +++ b/driver/src/test/java/org/neo4j/driver/internal/messaging/request/HelloMessageTest.java @@ -39,7 +39,7 @@ void shouldHaveCorrectMetadata() { authToken.put("user", value("Alice")); authToken.put("credentials", value("SecretPassword")); - HelloMessage message = new HelloMessage("MyDriver/1.0.2", authToken, Collections.emptyMap(), false); + HelloMessage message = new HelloMessage("MyDriver/1.0.2", authToken, Collections.emptyMap(), false, null); Map expectedMetadata = new HashMap<>(authToken); expectedMetadata.put("user_agent", value("MyDriver/1.0.2")); @@ -57,7 +57,7 @@ void shouldHaveCorrectRoutingContext() { routingContext.put("region", "China"); routingContext.put("speed", "Slow"); - HelloMessage message = new HelloMessage("MyDriver/1.0.2", authToken, routingContext, false); + HelloMessage message = new HelloMessage("MyDriver/1.0.2", authToken, routingContext, false, null); Map expectedMetadata = new HashMap<>(authToken); expectedMetadata.put("user_agent", value("MyDriver/1.0.2")); @@ -71,7 +71,7 @@ void shouldNotExposeCredentialsInToString() { authToken.put(PRINCIPAL_KEY, value("Alice")); authToken.put(CREDENTIALS_KEY, value("SecretPassword")); - HelloMessage message = new HelloMessage("MyDriver/1.0.2", authToken, Collections.emptyMap(), false); + HelloMessage message = new HelloMessage("MyDriver/1.0.2", authToken, Collections.emptyMap(), false, null); assertThat(message.toString(), not(containsString("SecretPassword"))); } diff --git a/driver/src/test/java/org/neo4j/driver/internal/messaging/request/TransactionMetadataBuilderTest.java b/driver/src/test/java/org/neo4j/driver/internal/messaging/request/TransactionMetadataBuilderTest.java index 8cf2fce601..1257474a8d 100644 --- a/driver/src/test/java/org/neo4j/driver/internal/messaging/request/TransactionMetadataBuilderTest.java +++ b/driver/src/test/java/org/neo4j/driver/internal/messaging/request/TransactionMetadataBuilderTest.java @@ -60,7 +60,7 @@ void shouldHaveCorrectMetadata(AccessMode mode) { Duration txTimeout = Duration.ofSeconds(7); Map metadata = - buildMetadata(txTimeout, txMetadata, defaultDatabase(), mode, bookmarks, null, null); + buildMetadata(txTimeout, txMetadata, defaultDatabase(), mode, bookmarks, null, null, null); Map expectedMetadata = new HashMap<>(); expectedMetadata.put( @@ -88,7 +88,7 @@ void shouldHaveCorrectMetadataForDatabaseName(String databaseName) { Duration txTimeout = Duration.ofSeconds(7); Map metadata = - buildMetadata(txTimeout, txMetadata, database(databaseName), WRITE, bookmarks, null, null); + buildMetadata(txTimeout, txMetadata, database(databaseName), WRITE, bookmarks, null, null, null); Map expectedMetadata = new HashMap<>(); expectedMetadata.put( @@ -103,7 +103,7 @@ void shouldHaveCorrectMetadataForDatabaseName(String databaseName) { @Test void shouldNotHaveMetadataForDatabaseNameWhenIsNull() { Map metadata = - buildMetadata(null, null, defaultDatabase(), WRITE, Collections.emptySet(), null, null); + buildMetadata(null, null, defaultDatabase(), WRITE, Collections.emptySet(), null, null, null); assertTrue(metadata.isEmpty()); } } diff --git a/driver/src/test/java/org/neo4j/driver/internal/messaging/v3/BoltProtocolV3Test.java b/driver/src/test/java/org/neo4j/driver/internal/messaging/v3/BoltProtocolV3Test.java index b9971c2495..a434897ac6 100644 --- a/driver/src/test/java/org/neo4j/driver/internal/messaging/v3/BoltProtocolV3Test.java +++ b/driver/src/test/java/org/neo4j/driver/internal/messaging/v3/BoltProtocolV3Test.java @@ -136,7 +136,7 @@ void shouldCreateMessageFormat() { void shouldInitializeChannel() { ChannelPromise promise = channel.newPromise(); - protocol.initializeChannel("MyDriver/0.0.1", dummyAuthToken(), RoutingContext.EMPTY, promise); + protocol.initializeChannel("MyDriver/0.0.1", dummyAuthToken(), RoutingContext.EMPTY, promise, null); assertThat(channel.outboundMessages(), hasSize(1)); assertThat(channel.outboundMessages().poll(), instanceOf(HelloMessage.class)); @@ -166,7 +166,7 @@ void shouldPrepareToCloseChannel() { void shouldFailToInitializeChannelWhenErrorIsReceived() { ChannelPromise promise = channel.newPromise(); - protocol.initializeChannel("MyDriver/2.2.1", dummyAuthToken(), RoutingContext.EMPTY, promise); + protocol.initializeChannel("MyDriver/2.2.1", dummyAuthToken(), RoutingContext.EMPTY, promise, null); assertThat(channel.outboundMessages(), hasSize(1)); assertThat(channel.outboundMessages().poll(), instanceOf(HelloMessage.class)); @@ -184,7 +184,7 @@ void shouldBeginTransactionWithoutBookmark() { Connection connection = connectionMock(protocol); CompletionStage stage = - protocol.beginTransaction(connection, Collections.emptySet(), TransactionConfig.empty(), null); + protocol.beginTransaction(connection, Collections.emptySet(), TransactionConfig.empty(), null, null); verify(connection) .writeAndFlush( @@ -194,6 +194,7 @@ void shouldBeginTransactionWithoutBookmark() { defaultDatabase(), WRITE, null, + null, null)), any(BeginTxResponseHandler.class)); assertNull(await(stage)); @@ -204,12 +205,13 @@ void shouldBeginTransactionWithBookmarks() { Connection connection = connectionMock(protocol); Set bookmarks = Collections.singleton(InternalBookmark.parse("neo4j:bookmark:v1:tx100")); - CompletionStage stage = protocol.beginTransaction(connection, bookmarks, TransactionConfig.empty(), null); + CompletionStage stage = + protocol.beginTransaction(connection, bookmarks, TransactionConfig.empty(), null, null); verify(connection) .writeAndFlush( eq(new BeginMessage( - bookmarks, TransactionConfig.empty(), defaultDatabase(), WRITE, null, null)), + bookmarks, TransactionConfig.empty(), defaultDatabase(), WRITE, null, null, null)), any(BeginTxResponseHandler.class)); assertNull(await(stage)); } @@ -218,11 +220,13 @@ void shouldBeginTransactionWithBookmarks() { void shouldBeginTransactionWithConfig() { Connection connection = connectionMock(protocol); - CompletionStage stage = protocol.beginTransaction(connection, Collections.emptySet(), txConfig, null); + CompletionStage stage = + protocol.beginTransaction(connection, Collections.emptySet(), txConfig, null, null); verify(connection) .writeAndFlush( - eq(new BeginMessage(Collections.emptySet(), txConfig, defaultDatabase(), WRITE, null, null)), + eq(new BeginMessage( + Collections.emptySet(), txConfig, defaultDatabase(), WRITE, null, null, null)), any(BeginTxResponseHandler.class)); assertNull(await(stage)); } @@ -232,11 +236,11 @@ void shouldBeginTransactionWithBookmarksAndConfig() { Connection connection = connectionMock(protocol); Set bookmarks = Collections.singleton(InternalBookmark.parse("neo4j:bookmark:v1:tx4242")); - CompletionStage stage = protocol.beginTransaction(connection, bookmarks, txConfig, null); + CompletionStage stage = protocol.beginTransaction(connection, bookmarks, txConfig, null, null); verify(connection) .writeAndFlush( - eq(new BeginMessage(bookmarks, txConfig, defaultDatabase(), WRITE, null, null)), + eq(new BeginMessage(bookmarks, txConfig, defaultDatabase(), WRITE, null, null, null)), any(BeginTxResponseHandler.class)); assertNull(await(stage)); } @@ -342,7 +346,7 @@ void databaseNameForAutoCommitTransactions() { @Test void shouldNotSupportDatabaseNameInBeginTransaction() { CompletionStage txStage = protocol.beginTransaction( - connectionMock("foo", protocol), Collections.emptySet(), TransactionConfig.empty(), null); + connectionMock("foo", protocol), Collections.emptySet(), TransactionConfig.empty(), null, null); ClientException e = assertThrows(ClientException.class, () -> await(txStage)); assertThat(e.getMessage(), startsWith("Database name parameter for selecting database is not supported")); @@ -358,7 +362,8 @@ void shouldNotSupportDatabaseNameForAutoCommitTransactions() { Collections.emptySet(), (ignored) -> {}, TransactionConfig.empty(), - UNLIMITED_FETCH_SIZE)); + UNLIMITED_FETCH_SIZE, + null)); assertThat(e.getMessage(), startsWith("Database name parameter for selecting database is not supported")); } @@ -373,10 +378,11 @@ protected void testDatabaseNameSupport(boolean autoCommitTx) { Collections.emptySet(), (ignored) -> {}, TransactionConfig.empty(), - UNLIMITED_FETCH_SIZE)); + UNLIMITED_FETCH_SIZE, + null)); } else { CompletionStage txStage = protocol.beginTransaction( - connectionMock("foo", protocol), Collections.emptySet(), TransactionConfig.empty(), null); + connectionMock("foo", protocol), Collections.emptySet(), TransactionConfig.empty(), null, null); e = assertThrows(ClientException.class, () -> await(txStage)); } @@ -424,7 +430,7 @@ protected void testRunAndWaitForRunResponse(boolean autoCommitTx, TransactionCon CompletionStage cursorStage; if (autoCommitTx) { cursorStage = protocol.runInAutoCommitTransaction( - connection, QUERY, initialBookmarks, (ignored) -> {}, config, UNLIMITED_FETCH_SIZE) + connection, QUERY, initialBookmarks, (ignored) -> {}, config, UNLIMITED_FETCH_SIZE, null) .asyncResult(); } else { cursorStage = protocol.runInUnmanagedTransaction( @@ -452,7 +458,7 @@ protected void testSuccessfulRunInAutoCommitTxWithWaitingForResponse( Consumer bookmarkConsumer = mock(Consumer.class); CompletableFuture cursorFuture = protocol.runInAutoCommitTransaction( - connection, QUERY, bookmarks, bookmarkConsumer, config, UNLIMITED_FETCH_SIZE) + connection, QUERY, bookmarks, bookmarkConsumer, config, UNLIMITED_FETCH_SIZE, null) .asyncResult() .toCompletableFuture(); assertFalse(cursorFuture.isDone()); @@ -475,7 +481,7 @@ protected void testFailedRunInAutoCommitTxWithWaitingForResponse( Consumer bookmarkConsumer = mock(Consumer.class); CompletableFuture cursorFuture = protocol.runInAutoCommitTransaction( - connection, QUERY, bookmarks, bookmarkConsumer, config, UNLIMITED_FETCH_SIZE) + connection, QUERY, bookmarks, bookmarkConsumer, config, UNLIMITED_FETCH_SIZE, null) .asyncResult() .toCompletableFuture(); assertFalse(cursorFuture.isDone()); @@ -507,7 +513,7 @@ private static ResponseHandlers verifyRunInvoked( RunWithMetadataMessage expectedMessage; if (session) { expectedMessage = RunWithMetadataMessage.autoCommitTxRunMessage( - QUERY, config, defaultDatabase(), mode, bookmarks, null); + QUERY, config, defaultDatabase(), mode, bookmarks, null, null); } else { expectedMessage = RunWithMetadataMessage.unmanagedTxRunMessage(QUERY); } diff --git a/driver/src/test/java/org/neo4j/driver/internal/messaging/v3/MessageWriterV3Test.java b/driver/src/test/java/org/neo4j/driver/internal/messaging/v3/MessageWriterV3Test.java index 7b09069219..1faaebece0 100644 --- a/driver/src/test/java/org/neo4j/driver/internal/messaging/v3/MessageWriterV3Test.java +++ b/driver/src/test/java/org/neo4j/driver/internal/messaging/v3/MessageWriterV3Test.java @@ -100,7 +100,8 @@ protected Stream supportedMessages() { "MyDriver/1.2.3", ((InternalAuthToken) basic("neo4j", "neo4j")).toMap(), Collections.emptyMap(), - false), + false, + null), GOODBYE, new BeginMessage( Collections.singleton(InternalBookmark.parse("neo4j:bookmark:v1:tx123")), @@ -109,6 +110,7 @@ protected Stream supportedMessages() { READ, defaultDatabase(), null, + null, null), new BeginMessage( Collections.singleton(InternalBookmark.parse("neo4j:bookmark:v1:tx123")), @@ -117,6 +119,7 @@ protected Stream supportedMessages() { WRITE, defaultDatabase(), null, + null, null), COMMIT, ROLLBACK, @@ -127,6 +130,7 @@ protected Stream supportedMessages() { defaultDatabase(), READ, Collections.singleton(InternalBookmark.parse("neo4j:bookmark:v1:tx1")), + null, null), autoCommitTxRunMessage( new Query("RETURN 1"), @@ -135,6 +139,7 @@ protected Stream supportedMessages() { defaultDatabase(), WRITE, Collections.singleton(InternalBookmark.parse("neo4j:bookmark:v1:tx1")), + null, null), unmanagedTxRunMessage(new Query("RETURN 1")), PULL_ALL, @@ -149,6 +154,7 @@ protected Stream supportedMessages() { defaultDatabase(), READ, Collections.emptySet(), + null, null), autoCommitTxRunMessage( new Query("RETURN $x", singletonMap("x", value(ZonedDateTime.now()))), @@ -157,6 +163,7 @@ protected Stream supportedMessages() { defaultDatabase(), WRITE, Collections.emptySet(), + null, null), unmanagedTxRunMessage(new Query("RETURN $x", singletonMap("x", point(42, 1, 2, 3))))); } diff --git a/driver/src/test/java/org/neo4j/driver/internal/messaging/v4/BoltProtocolV4Test.java b/driver/src/test/java/org/neo4j/driver/internal/messaging/v4/BoltProtocolV4Test.java index 197de1e191..e6701924b7 100644 --- a/driver/src/test/java/org/neo4j/driver/internal/messaging/v4/BoltProtocolV4Test.java +++ b/driver/src/test/java/org/neo4j/driver/internal/messaging/v4/BoltProtocolV4Test.java @@ -131,7 +131,7 @@ void shouldCreateMessageFormat() { void shouldInitializeChannel() { ChannelPromise promise = channel.newPromise(); - protocol.initializeChannel("MyDriver/0.0.1", dummyAuthToken(), RoutingContext.EMPTY, promise); + protocol.initializeChannel("MyDriver/0.0.1", dummyAuthToken(), RoutingContext.EMPTY, promise, null); assertThat(channel.outboundMessages(), hasSize(1)); assertThat(channel.outboundMessages().poll(), instanceOf(HelloMessage.class)); @@ -161,7 +161,7 @@ void shouldPrepareToCloseChannel() { void shouldFailToInitializeChannelWhenErrorIsReceived() { ChannelPromise promise = channel.newPromise(); - protocol.initializeChannel("MyDriver/2.2.1", dummyAuthToken(), RoutingContext.EMPTY, promise); + protocol.initializeChannel("MyDriver/2.2.1", dummyAuthToken(), RoutingContext.EMPTY, promise, null); assertThat(channel.outboundMessages(), hasSize(1)); assertThat(channel.outboundMessages().poll(), instanceOf(HelloMessage.class)); @@ -179,7 +179,7 @@ void shouldBeginTransactionWithoutBookmark() { Connection connection = connectionMock(protocol); CompletionStage stage = - protocol.beginTransaction(connection, Collections.emptySet(), TransactionConfig.empty(), null); + protocol.beginTransaction(connection, Collections.emptySet(), TransactionConfig.empty(), null, null); verify(connection) .writeAndFlush( @@ -189,6 +189,7 @@ void shouldBeginTransactionWithoutBookmark() { defaultDatabase(), WRITE, null, + null, null)), any(BeginTxResponseHandler.class)); assertNull(await(stage)); @@ -199,12 +200,13 @@ void shouldBeginTransactionWithBookmarks() { Connection connection = connectionMock(protocol); Set bookmarks = Collections.singleton(InternalBookmark.parse("neo4j:bookmark:v1:tx100")); - CompletionStage stage = protocol.beginTransaction(connection, bookmarks, TransactionConfig.empty(), null); + CompletionStage stage = + protocol.beginTransaction(connection, bookmarks, TransactionConfig.empty(), null, null); verify(connection) .writeAndFlush( eq(new BeginMessage( - bookmarks, TransactionConfig.empty(), defaultDatabase(), WRITE, null, null)), + bookmarks, TransactionConfig.empty(), defaultDatabase(), WRITE, null, null, null)), any(BeginTxResponseHandler.class)); assertNull(await(stage)); } @@ -213,11 +215,13 @@ void shouldBeginTransactionWithBookmarks() { void shouldBeginTransactionWithConfig() { Connection connection = connectionMock(protocol); - CompletionStage stage = protocol.beginTransaction(connection, Collections.emptySet(), txConfig, null); + CompletionStage stage = + protocol.beginTransaction(connection, Collections.emptySet(), txConfig, null, null); verify(connection) .writeAndFlush( - eq(new BeginMessage(Collections.emptySet(), txConfig, defaultDatabase(), WRITE, null, null)), + eq(new BeginMessage( + Collections.emptySet(), txConfig, defaultDatabase(), WRITE, null, null, null)), any(BeginTxResponseHandler.class)); assertNull(await(stage)); } @@ -227,11 +231,11 @@ void shouldBeginTransactionWithBookmarksAndConfig() { Connection connection = connectionMock(protocol); Set bookmarks = Collections.singleton(InternalBookmark.parse("neo4j:bookmark:v1:tx4242")); - CompletionStage stage = protocol.beginTransaction(connection, bookmarks, txConfig, null); + CompletionStage stage = protocol.beginTransaction(connection, bookmarks, txConfig, null, null); verify(connection) .writeAndFlush( - eq(new BeginMessage(bookmarks, txConfig, defaultDatabase(), WRITE, null, null)), + eq(new BeginMessage(bookmarks, txConfig, defaultDatabase(), WRITE, null, null, null)), any(BeginTxResponseHandler.class)); assertNull(await(stage)); } @@ -337,7 +341,7 @@ void databaseNameForAutoCommitTransactions() { @Test void shouldSupportDatabaseNameInBeginTransaction() { CompletionStage txStage = protocol.beginTransaction( - connectionMock("foo", protocol), Collections.emptySet(), TransactionConfig.empty(), null); + connectionMock("foo", protocol), Collections.emptySet(), TransactionConfig.empty(), null, null); assertDoesNotThrow(() -> await(txStage)); } @@ -350,7 +354,8 @@ void shouldNotSupportDatabaseNameForAutoCommitTransactions() { Collections.emptySet(), (ignored) -> {}, TransactionConfig.empty(), - UNLIMITED_FETCH_SIZE)); + UNLIMITED_FETCH_SIZE, + null)); } private BoltProtocol createProtocol() { @@ -373,7 +378,7 @@ protected void testFailedRunInAutoCommitTxWithWaitingForResponse( Consumer bookmarkConsumer = mock(Consumer.class); CompletableFuture cursorFuture = protocol.runInAutoCommitTransaction( - connection, QUERY, bookmarks, bookmarkConsumer, config, UNLIMITED_FETCH_SIZE) + connection, QUERY, bookmarks, bookmarkConsumer, config, UNLIMITED_FETCH_SIZE, null) .asyncResult() .toCompletableFuture(); @@ -400,7 +405,7 @@ protected void testSuccessfulRunInAutoCommitTxWithWaitingForResponse( Consumer bookmarkConsumer = mock(Consumer.class); CompletableFuture cursorFuture = protocol.runInAutoCommitTransaction( - connection, QUERY, bookmarks, bookmarkConsumer, config, UNLIMITED_FETCH_SIZE) + connection, QUERY, bookmarks, bookmarkConsumer, config, UNLIMITED_FETCH_SIZE, null) .asyncResult() .toCompletableFuture(); @@ -457,7 +462,7 @@ protected void testRunAndWaitForRunResponse(boolean autoCommitTx, TransactionCon CompletionStage cursorStage; if (autoCommitTx) { cursorStage = protocol.runInAutoCommitTransaction( - connection, QUERY, initialBookmarks, (ignored) -> {}, config, UNLIMITED_FETCH_SIZE) + connection, QUERY, initialBookmarks, (ignored) -> {}, config, UNLIMITED_FETCH_SIZE, null) .asyncResult(); } else { cursorStage = protocol.runInUnmanagedTransaction( @@ -487,15 +492,16 @@ protected void testDatabaseNameSupport(boolean autoCommitTx) { Collections.emptySet(), (ignored) -> {}, TransactionConfig.empty(), - UNLIMITED_FETCH_SIZE); + UNLIMITED_FETCH_SIZE, + null); CompletionStage resultStage = factory.asyncResult(); ResponseHandler runHandler = verifySessionRunInvoked( connection, Collections.emptySet(), TransactionConfig.empty(), AccessMode.WRITE, database("foo")); runHandler.onSuccess(emptyMap()); await(resultStage); } else { - CompletionStage txStage = - protocol.beginTransaction(connection, Collections.emptySet(), TransactionConfig.empty(), null); + CompletionStage txStage = protocol.beginTransaction( + connection, Collections.emptySet(), TransactionConfig.empty(), null, null); await(txStage); verifyBeginInvoked( connection, Collections.emptySet(), TransactionConfig.empty(), AccessMode.WRITE, database("foo")); @@ -513,7 +519,7 @@ private ResponseHandler verifySessionRunInvoked( AccessMode mode, DatabaseName databaseName) { RunWithMetadataMessage runMessage = - RunWithMetadataMessage.autoCommitTxRunMessage(QUERY, config, databaseName, mode, bookmarks, null); + RunWithMetadataMessage.autoCommitTxRunMessage(QUERY, config, databaseName, mode, bookmarks, null, null); return verifyRunInvoked(connection, runMessage); } @@ -537,7 +543,7 @@ private void verifyBeginInvoked( AccessMode mode, DatabaseName databaseName) { ArgumentCaptor beginHandlerCaptor = ArgumentCaptor.forClass(ResponseHandler.class); - BeginMessage beginMessage = new BeginMessage(bookmarks, config, databaseName, mode, null, null); + BeginMessage beginMessage = new BeginMessage(bookmarks, config, databaseName, mode, null, null, null); verify(connection).writeAndFlush(eq(beginMessage), beginHandlerCaptor.capture()); assertThat(beginHandlerCaptor.getValue(), instanceOf(BeginTxResponseHandler.class)); } diff --git a/driver/src/test/java/org/neo4j/driver/internal/messaging/v4/MessageWriterV4Test.java b/driver/src/test/java/org/neo4j/driver/internal/messaging/v4/MessageWriterV4Test.java index 00e1d772dc..c355cd68d4 100644 --- a/driver/src/test/java/org/neo4j/driver/internal/messaging/v4/MessageWriterV4Test.java +++ b/driver/src/test/java/org/neo4j/driver/internal/messaging/v4/MessageWriterV4Test.java @@ -108,7 +108,8 @@ protected Stream supportedMessages() { "MyDriver/1.2.3", ((InternalAuthToken) basic("neo4j", "neo4j")).toMap(), Collections.emptyMap(), - false), + false, + null), GOODBYE, new BeginMessage( Collections.singleton(InternalBookmark.parse("neo4j:bookmark:v1:tx123")), @@ -117,6 +118,7 @@ protected Stream supportedMessages() { READ, defaultDatabase(), null, + null, null), new BeginMessage( Collections.singleton(InternalBookmark.parse("neo4j:bookmark:v1:tx123")), @@ -125,6 +127,7 @@ protected Stream supportedMessages() { WRITE, database("foo"), null, + null, null), COMMIT, ROLLBACK, @@ -136,6 +139,7 @@ protected Stream supportedMessages() { defaultDatabase(), READ, Collections.singleton(InternalBookmark.parse("neo4j:bookmark:v1:tx1")), + null, null), autoCommitTxRunMessage( new Query("RETURN 1"), @@ -144,6 +148,7 @@ protected Stream supportedMessages() { database("foo"), WRITE, Collections.singleton(InternalBookmark.parse("neo4j:bookmark:v1:tx1")), + null, null), unmanagedTxRunMessage(new Query("RETURN 1")), @@ -155,6 +160,7 @@ protected Stream supportedMessages() { defaultDatabase(), READ, Collections.emptySet(), + null, null), autoCommitTxRunMessage( new Query("RETURN $x", singletonMap("x", value(ZonedDateTime.now()))), @@ -163,6 +169,7 @@ protected Stream supportedMessages() { database("foo"), WRITE, Collections.emptySet(), + null, null), unmanagedTxRunMessage(new Query("RETURN $x", singletonMap("x", point(42, 1, 2, 3))))); } diff --git a/driver/src/test/java/org/neo4j/driver/internal/messaging/v41/BoltProtocolV41Test.java b/driver/src/test/java/org/neo4j/driver/internal/messaging/v41/BoltProtocolV41Test.java index 0840ae0bd7..0bff6bdfa2 100644 --- a/driver/src/test/java/org/neo4j/driver/internal/messaging/v41/BoltProtocolV41Test.java +++ b/driver/src/test/java/org/neo4j/driver/internal/messaging/v41/BoltProtocolV41Test.java @@ -135,7 +135,7 @@ void shouldCreateMessageFormat() { void shouldInitializeChannel() { ChannelPromise promise = channel.newPromise(); - protocol.initializeChannel("MyDriver/0.0.1", dummyAuthToken(), RoutingContext.EMPTY, promise); + protocol.initializeChannel("MyDriver/0.0.1", dummyAuthToken(), RoutingContext.EMPTY, promise, null); assertThat(channel.outboundMessages(), hasSize(1)); assertThat(channel.outboundMessages().poll(), instanceOf(HelloMessage.class)); @@ -165,7 +165,7 @@ void shouldPrepareToCloseChannel() { void shouldFailToInitializeChannelWhenErrorIsReceived() { ChannelPromise promise = channel.newPromise(); - protocol.initializeChannel("MyDriver/2.2.1", dummyAuthToken(), RoutingContext.EMPTY, promise); + protocol.initializeChannel("MyDriver/2.2.1", dummyAuthToken(), RoutingContext.EMPTY, promise, null); assertThat(channel.outboundMessages(), hasSize(1)); assertThat(channel.outboundMessages().poll(), instanceOf(HelloMessage.class)); @@ -183,7 +183,7 @@ void shouldBeginTransactionWithoutBookmark() { Connection connection = connectionMock(protocol); CompletionStage stage = - protocol.beginTransaction(connection, Collections.emptySet(), TransactionConfig.empty(), null); + protocol.beginTransaction(connection, Collections.emptySet(), TransactionConfig.empty(), null, null); verify(connection) .writeAndFlush( @@ -193,6 +193,7 @@ void shouldBeginTransactionWithoutBookmark() { defaultDatabase(), WRITE, null, + null, null)), any(BeginTxResponseHandler.class)); assertNull(await(stage)); @@ -203,12 +204,13 @@ void shouldBeginTransactionWithBookmarks() { Connection connection = connectionMock(protocol); Set bookmarks = Collections.singleton(InternalBookmark.parse("neo4j:bookmark:v1:tx100")); - CompletionStage stage = protocol.beginTransaction(connection, bookmarks, TransactionConfig.empty(), null); + CompletionStage stage = + protocol.beginTransaction(connection, bookmarks, TransactionConfig.empty(), null, null); verify(connection) .writeAndFlush( eq(new BeginMessage( - bookmarks, TransactionConfig.empty(), defaultDatabase(), WRITE, null, null)), + bookmarks, TransactionConfig.empty(), defaultDatabase(), WRITE, null, null, null)), any(BeginTxResponseHandler.class)); assertNull(await(stage)); } @@ -217,11 +219,13 @@ void shouldBeginTransactionWithBookmarks() { void shouldBeginTransactionWithConfig() { Connection connection = connectionMock(protocol); - CompletionStage stage = protocol.beginTransaction(connection, Collections.emptySet(), txConfig, null); + CompletionStage stage = + protocol.beginTransaction(connection, Collections.emptySet(), txConfig, null, null); verify(connection) .writeAndFlush( - eq(new BeginMessage(Collections.emptySet(), txConfig, defaultDatabase(), WRITE, null, null)), + eq(new BeginMessage( + Collections.emptySet(), txConfig, defaultDatabase(), WRITE, null, null, null)), any(BeginTxResponseHandler.class)); assertNull(await(stage)); } @@ -231,11 +235,11 @@ void shouldBeginTransactionWithBookmarksAndConfig() { Connection connection = connectionMock(protocol); Set bookmarks = Collections.singleton(InternalBookmark.parse("neo4j:bookmark:v1:tx4242")); - CompletionStage stage = protocol.beginTransaction(connection, bookmarks, txConfig, null); + CompletionStage stage = protocol.beginTransaction(connection, bookmarks, txConfig, null, null); verify(connection) .writeAndFlush( - eq(new BeginMessage(bookmarks, txConfig, defaultDatabase(), WRITE, null, null)), + eq(new BeginMessage(bookmarks, txConfig, defaultDatabase(), WRITE, null, null, null)), any(BeginTxResponseHandler.class)); assertNull(await(stage)); } @@ -341,7 +345,7 @@ void databaseNameForAutoCommitTransactions() { @Test void shouldSupportDatabaseNameInBeginTransaction() { CompletionStage txStage = protocol.beginTransaction( - connectionMock("foo", protocol), Collections.emptySet(), TransactionConfig.empty(), null); + connectionMock("foo", protocol), Collections.emptySet(), TransactionConfig.empty(), null, null); assertDoesNotThrow(() -> await(txStage)); } @@ -354,7 +358,8 @@ void shouldNotSupportDatabaseNameForAutoCommitTransactions() { Collections.emptySet(), (ignored) -> {}, TransactionConfig.empty(), - UNLIMITED_FETCH_SIZE)); + UNLIMITED_FETCH_SIZE, + null)); } private Class expectedMessageFormatType() { @@ -369,7 +374,7 @@ private void testFailedRunInAutoCommitTxWithWaitingForResponse( Consumer bookmarkConsumer = mock(Consumer.class); CompletableFuture cursorFuture = protocol.runInAutoCommitTransaction( - connection, QUERY, bookmarks, bookmarkConsumer, config, UNLIMITED_FETCH_SIZE) + connection, QUERY, bookmarks, bookmarkConsumer, config, UNLIMITED_FETCH_SIZE, null) .asyncResult() .toCompletableFuture(); @@ -396,7 +401,7 @@ private void testSuccessfulRunInAutoCommitTxWithWaitingForResponse( Consumer bookmarkConsumer = mock(Consumer.class); CompletableFuture cursorFuture = protocol.runInAutoCommitTransaction( - connection, QUERY, bookmarks, bookmarkConsumer, config, UNLIMITED_FETCH_SIZE) + connection, QUERY, bookmarks, bookmarkConsumer, config, UNLIMITED_FETCH_SIZE, null) .asyncResult() .toCompletableFuture(); @@ -452,7 +457,7 @@ private void testRunAndWaitForRunResponse(boolean autoCommitTx, TransactionConfi CompletionStage cursorStage; if (autoCommitTx) { cursorStage = protocol.runInAutoCommitTransaction( - connection, QUERY, initialBookmarks, (ignored) -> {}, config, UNLIMITED_FETCH_SIZE) + connection, QUERY, initialBookmarks, (ignored) -> {}, config, UNLIMITED_FETCH_SIZE, null) .asyncResult(); } else { cursorStage = protocol.runInUnmanagedTransaction( @@ -480,7 +485,8 @@ private void testDatabaseNameSupport(boolean autoCommitTx) { Collections.emptySet(), (ignored) -> {}, TransactionConfig.empty(), - UNLIMITED_FETCH_SIZE); + UNLIMITED_FETCH_SIZE, + null); CompletionStage resultStage = factory.asyncResult(); ResponseHandler runHandler = verifySessionRunInvoked( connection, Collections.emptySet(), TransactionConfig.empty(), AccessMode.WRITE, database("foo")); @@ -489,8 +495,8 @@ private void testDatabaseNameSupport(boolean autoCommitTx) { verifySessionRunInvoked( connection, Collections.emptySet(), TransactionConfig.empty(), AccessMode.WRITE, database("foo")); } else { - CompletionStage txStage = - protocol.beginTransaction(connection, Collections.emptySet(), TransactionConfig.empty(), null); + CompletionStage txStage = protocol.beginTransaction( + connection, Collections.emptySet(), TransactionConfig.empty(), null, null); await(txStage); verifyBeginInvoked( connection, Collections.emptySet(), TransactionConfig.empty(), AccessMode.WRITE, database("foo")); @@ -508,7 +514,7 @@ private ResponseHandler verifySessionRunInvoked( AccessMode mode, DatabaseName databaseName) { RunWithMetadataMessage runMessage = - RunWithMetadataMessage.autoCommitTxRunMessage(QUERY, config, databaseName, mode, bookmarks, null); + RunWithMetadataMessage.autoCommitTxRunMessage(QUERY, config, databaseName, mode, bookmarks, null, null); return verifyRunInvoked(connection, runMessage); } @@ -532,7 +538,7 @@ private void verifyBeginInvoked( AccessMode mode, DatabaseName databaseName) { ArgumentCaptor beginHandlerCaptor = ArgumentCaptor.forClass(ResponseHandler.class); - BeginMessage beginMessage = new BeginMessage(bookmarks, config, databaseName, mode, null, null); + BeginMessage beginMessage = new BeginMessage(bookmarks, config, databaseName, mode, null, null, null); verify(connection).writeAndFlush(eq(beginMessage), beginHandlerCaptor.capture()); assertThat(beginHandlerCaptor.getValue(), instanceOf(BeginTxResponseHandler.class)); } diff --git a/driver/src/test/java/org/neo4j/driver/internal/messaging/v41/MessageWriterV41Test.java b/driver/src/test/java/org/neo4j/driver/internal/messaging/v41/MessageWriterV41Test.java index d828091fcf..8f740825f5 100644 --- a/driver/src/test/java/org/neo4j/driver/internal/messaging/v41/MessageWriterV41Test.java +++ b/driver/src/test/java/org/neo4j/driver/internal/messaging/v41/MessageWriterV41Test.java @@ -107,7 +107,8 @@ protected Stream supportedMessages() { "MyDriver/1.2.3", ((InternalAuthToken) basic("neo4j", "neo4j")).toMap(), Collections.emptyMap(), - false), + false, + null), GOODBYE, new BeginMessage( Collections.singleton(InternalBookmark.parse("neo4j:bookmark:v1:tx123")), @@ -116,6 +117,7 @@ protected Stream supportedMessages() { READ, defaultDatabase(), null, + null, null), new BeginMessage( Collections.singleton(InternalBookmark.parse("neo4j:bookmark:v1:tx123")), @@ -124,6 +126,7 @@ protected Stream supportedMessages() { WRITE, database("foo"), null, + null, null), COMMIT, ROLLBACK, @@ -135,6 +138,7 @@ protected Stream supportedMessages() { defaultDatabase(), READ, Collections.singleton(InternalBookmark.parse("neo4j:bookmark:v1:tx1")), + null, null), autoCommitTxRunMessage( new Query("RETURN 1"), @@ -143,6 +147,7 @@ protected Stream supportedMessages() { database("foo"), WRITE, Collections.singleton(InternalBookmark.parse("neo4j:bookmark:v1:tx1")), + null, null), unmanagedTxRunMessage(new Query("RETURN 1")), @@ -154,6 +159,7 @@ protected Stream supportedMessages() { defaultDatabase(), READ, Collections.emptySet(), + null, null), autoCommitTxRunMessage( new Query("RETURN $x", singletonMap("x", value(ZonedDateTime.now()))), @@ -162,6 +168,7 @@ protected Stream supportedMessages() { database("foo"), WRITE, Collections.emptySet(), + null, null), unmanagedTxRunMessage(new Query("RETURN $x", singletonMap("x", point(42, 1, 2, 3))))); } diff --git a/driver/src/test/java/org/neo4j/driver/internal/messaging/v42/BoltProtocolV42Test.java b/driver/src/test/java/org/neo4j/driver/internal/messaging/v42/BoltProtocolV42Test.java index 257bc607c9..2ad341ba4b 100644 --- a/driver/src/test/java/org/neo4j/driver/internal/messaging/v42/BoltProtocolV42Test.java +++ b/driver/src/test/java/org/neo4j/driver/internal/messaging/v42/BoltProtocolV42Test.java @@ -135,7 +135,7 @@ void shouldCreateMessageFormat() { void shouldInitializeChannel() { ChannelPromise promise = channel.newPromise(); - protocol.initializeChannel("MyDriver/0.0.1", dummyAuthToken(), RoutingContext.EMPTY, promise); + protocol.initializeChannel("MyDriver/0.0.1", dummyAuthToken(), RoutingContext.EMPTY, promise, null); assertThat(channel.outboundMessages(), hasSize(1)); assertThat(channel.outboundMessages().poll(), instanceOf(HelloMessage.class)); @@ -165,7 +165,7 @@ void shouldPrepareToCloseChannel() { void shouldFailToInitializeChannelWhenErrorIsReceived() { ChannelPromise promise = channel.newPromise(); - protocol.initializeChannel("MyDriver/2.2.1", dummyAuthToken(), RoutingContext.EMPTY, promise); + protocol.initializeChannel("MyDriver/2.2.1", dummyAuthToken(), RoutingContext.EMPTY, promise, null); assertThat(channel.outboundMessages(), hasSize(1)); assertThat(channel.outboundMessages().poll(), instanceOf(HelloMessage.class)); @@ -183,7 +183,7 @@ void shouldBeginTransactionWithoutBookmark() { Connection connection = connectionMock(protocol); CompletionStage stage = - protocol.beginTransaction(connection, Collections.emptySet(), TransactionConfig.empty(), null); + protocol.beginTransaction(connection, Collections.emptySet(), TransactionConfig.empty(), null, null); verify(connection) .writeAndFlush( @@ -193,6 +193,7 @@ void shouldBeginTransactionWithoutBookmark() { defaultDatabase(), WRITE, null, + null, null)), any(BeginTxResponseHandler.class)); assertNull(await(stage)); @@ -203,12 +204,13 @@ void shouldBeginTransactionWithBookmarks() { Connection connection = connectionMock(protocol); Set bookmarks = Collections.singleton(InternalBookmark.parse("neo4j:bookmark:v1:tx100")); - CompletionStage stage = protocol.beginTransaction(connection, bookmarks, TransactionConfig.empty(), null); + CompletionStage stage = + protocol.beginTransaction(connection, bookmarks, TransactionConfig.empty(), null, null); verify(connection) .writeAndFlush( eq(new BeginMessage( - bookmarks, TransactionConfig.empty(), defaultDatabase(), WRITE, null, null)), + bookmarks, TransactionConfig.empty(), defaultDatabase(), WRITE, null, null, null)), any(BeginTxResponseHandler.class)); assertNull(await(stage)); } @@ -217,11 +219,13 @@ void shouldBeginTransactionWithBookmarks() { void shouldBeginTransactionWithConfig() { Connection connection = connectionMock(protocol); - CompletionStage stage = protocol.beginTransaction(connection, Collections.emptySet(), txConfig, null); + CompletionStage stage = + protocol.beginTransaction(connection, Collections.emptySet(), txConfig, null, null); verify(connection) .writeAndFlush( - eq(new BeginMessage(Collections.emptySet(), txConfig, defaultDatabase(), WRITE, null, null)), + eq(new BeginMessage( + Collections.emptySet(), txConfig, defaultDatabase(), WRITE, null, null, null)), any(BeginTxResponseHandler.class)); assertNull(await(stage)); } @@ -231,11 +235,11 @@ void shouldBeginTransactionWithBookmarksAndConfig() { Connection connection = connectionMock(protocol); Set bookmarks = Collections.singleton(InternalBookmark.parse("neo4j:bookmark:v1:tx4242")); - CompletionStage stage = protocol.beginTransaction(connection, bookmarks, txConfig, null); + CompletionStage stage = protocol.beginTransaction(connection, bookmarks, txConfig, null, null); verify(connection) .writeAndFlush( - eq(new BeginMessage(bookmarks, txConfig, defaultDatabase(), WRITE, null, null)), + eq(new BeginMessage(bookmarks, txConfig, defaultDatabase(), WRITE, null, null, null)), any(BeginTxResponseHandler.class)); assertNull(await(stage)); } @@ -341,7 +345,7 @@ void databaseNameForAutoCommitTransactions() { @Test void shouldSupportDatabaseNameInBeginTransaction() { CompletionStage txStage = protocol.beginTransaction( - connectionMock("foo", protocol), Collections.emptySet(), TransactionConfig.empty(), null); + connectionMock("foo", protocol), Collections.emptySet(), TransactionConfig.empty(), null, null); assertDoesNotThrow(() -> await(txStage)); } @@ -354,7 +358,8 @@ void shouldNotSupportDatabaseNameForAutoCommitTransactions() { Collections.emptySet(), (ignored) -> {}, TransactionConfig.empty(), - UNLIMITED_FETCH_SIZE)); + UNLIMITED_FETCH_SIZE, + null)); } private Class expectedMessageFormatType() { @@ -369,7 +374,7 @@ private void testFailedRunInAutoCommitTxWithWaitingForResponse( Consumer bookmarkConsumer = mock(Consumer.class); CompletableFuture cursorFuture = protocol.runInAutoCommitTransaction( - connection, QUERY, bookmarks, bookmarkConsumer, config, UNLIMITED_FETCH_SIZE) + connection, QUERY, bookmarks, bookmarkConsumer, config, UNLIMITED_FETCH_SIZE, null) .asyncResult() .toCompletableFuture(); @@ -395,7 +400,7 @@ private void testSuccessfulRunInAutoCommitTxWithWaitingForResponse( Consumer bookmarkConsumer = mock(Consumer.class); CompletableFuture cursorFuture = protocol.runInAutoCommitTransaction( - connection, QUERY, bookmarks, bookmarkConsumer, config, UNLIMITED_FETCH_SIZE) + connection, QUERY, bookmarks, bookmarkConsumer, config, UNLIMITED_FETCH_SIZE, null) .asyncResult() .toCompletableFuture(); @@ -451,7 +456,7 @@ private void testRunAndWaitForRunResponse(boolean autoCommitTx, TransactionConfi CompletionStage cursorStage; if (autoCommitTx) { cursorStage = protocol.runInAutoCommitTransaction( - connection, QUERY, initialBookmarks, (ignored) -> {}, config, UNLIMITED_FETCH_SIZE) + connection, QUERY, initialBookmarks, (ignored) -> {}, config, UNLIMITED_FETCH_SIZE, null) .asyncResult(); } else { cursorStage = protocol.runInUnmanagedTransaction( @@ -481,7 +486,8 @@ private void testDatabaseNameSupport(boolean autoCommitTx) { Collections.emptySet(), (ignored) -> {}, TransactionConfig.empty(), - UNLIMITED_FETCH_SIZE); + UNLIMITED_FETCH_SIZE, + null); CompletionStage resultStage = factory.asyncResult(); ResponseHandler runHandler = verifySessionRunInvoked( connection, Collections.emptySet(), TransactionConfig.empty(), AccessMode.WRITE, database("foo")); @@ -490,8 +496,8 @@ private void testDatabaseNameSupport(boolean autoCommitTx) { verifySessionRunInvoked( connection, Collections.emptySet(), TransactionConfig.empty(), AccessMode.WRITE, database("foo")); } else { - CompletionStage txStage = - protocol.beginTransaction(connection, Collections.emptySet(), TransactionConfig.empty(), null); + CompletionStage txStage = protocol.beginTransaction( + connection, Collections.emptySet(), TransactionConfig.empty(), null, null); await(txStage); verifyBeginInvoked( connection, Collections.emptySet(), TransactionConfig.empty(), AccessMode.WRITE, database("foo")); @@ -509,7 +515,7 @@ private ResponseHandler verifySessionRunInvoked( AccessMode mode, DatabaseName databaseName) { RunWithMetadataMessage runMessage = - RunWithMetadataMessage.autoCommitTxRunMessage(QUERY, config, databaseName, mode, bookmarks, null); + RunWithMetadataMessage.autoCommitTxRunMessage(QUERY, config, databaseName, mode, bookmarks, null, null); return verifyRunInvoked(connection, runMessage); } @@ -533,7 +539,7 @@ private void verifyBeginInvoked( AccessMode mode, DatabaseName databaseName) { ArgumentCaptor beginHandlerCaptor = ArgumentCaptor.forClass(ResponseHandler.class); - BeginMessage beginMessage = new BeginMessage(bookmarks, config, databaseName, mode, null, null); + BeginMessage beginMessage = new BeginMessage(bookmarks, config, databaseName, mode, null, null, null); verify(connection).writeAndFlush(eq(beginMessage), beginHandlerCaptor.capture()); assertThat(beginHandlerCaptor.getValue(), instanceOf(BeginTxResponseHandler.class)); } diff --git a/driver/src/test/java/org/neo4j/driver/internal/messaging/v42/MessageWriterV42Test.java b/driver/src/test/java/org/neo4j/driver/internal/messaging/v42/MessageWriterV42Test.java index 7e77ca4553..aca14b8f9c 100644 --- a/driver/src/test/java/org/neo4j/driver/internal/messaging/v42/MessageWriterV42Test.java +++ b/driver/src/test/java/org/neo4j/driver/internal/messaging/v42/MessageWriterV42Test.java @@ -107,7 +107,8 @@ protected Stream supportedMessages() { "MyDriver/1.2.3", ((InternalAuthToken) basic("neo4j", "neo4j")).toMap(), Collections.emptyMap(), - false), + false, + null), GOODBYE, new BeginMessage( Collections.singleton(InternalBookmark.parse("neo4j:bookmark:v1:tx123")), @@ -116,6 +117,7 @@ protected Stream supportedMessages() { READ, defaultDatabase(), null, + null, null), new BeginMessage( Collections.singleton(InternalBookmark.parse("neo4j:bookmark:v1:tx123")), @@ -124,6 +126,7 @@ protected Stream supportedMessages() { WRITE, database("foo"), null, + null, null), COMMIT, ROLLBACK, @@ -135,6 +138,7 @@ protected Stream supportedMessages() { defaultDatabase(), READ, Collections.singleton(InternalBookmark.parse("neo4j:bookmark:v1:tx1")), + null, null), autoCommitTxRunMessage( new Query("RETURN 1"), @@ -143,6 +147,7 @@ protected Stream supportedMessages() { database("foo"), WRITE, Collections.singleton(InternalBookmark.parse("neo4j:bookmark:v1:tx1")), + null, null), unmanagedTxRunMessage(new Query("RETURN 1")), @@ -154,6 +159,7 @@ protected Stream supportedMessages() { defaultDatabase(), READ, Collections.emptySet(), + null, null), autoCommitTxRunMessage( new Query("RETURN $x", singletonMap("x", value(ZonedDateTime.now()))), @@ -162,6 +168,7 @@ protected Stream supportedMessages() { database("foo"), WRITE, Collections.emptySet(), + null, null), unmanagedTxRunMessage(new Query("RETURN $x", singletonMap("x", point(42, 1, 2, 3))))); } diff --git a/driver/src/test/java/org/neo4j/driver/internal/messaging/v43/BoltProtocolV43Test.java b/driver/src/test/java/org/neo4j/driver/internal/messaging/v43/BoltProtocolV43Test.java index e3cab020a8..701ca503e9 100644 --- a/driver/src/test/java/org/neo4j/driver/internal/messaging/v43/BoltProtocolV43Test.java +++ b/driver/src/test/java/org/neo4j/driver/internal/messaging/v43/BoltProtocolV43Test.java @@ -134,7 +134,7 @@ void shouldCreateMessageFormat() { void shouldInitializeChannel() { ChannelPromise promise = channel.newPromise(); - protocol.initializeChannel("MyDriver/0.0.1", dummyAuthToken(), RoutingContext.EMPTY, promise); + protocol.initializeChannel("MyDriver/0.0.1", dummyAuthToken(), RoutingContext.EMPTY, promise, null); assertThat(channel.outboundMessages(), hasSize(1)); assertThat(channel.outboundMessages().poll(), instanceOf(HelloMessage.class)); @@ -164,7 +164,7 @@ void shouldPrepareToCloseChannel() { void shouldFailToInitializeChannelWhenErrorIsReceived() { ChannelPromise promise = channel.newPromise(); - protocol.initializeChannel("MyDriver/2.2.1", dummyAuthToken(), RoutingContext.EMPTY, promise); + protocol.initializeChannel("MyDriver/2.2.1", dummyAuthToken(), RoutingContext.EMPTY, promise, null); assertThat(channel.outboundMessages(), hasSize(1)); assertThat(channel.outboundMessages().poll(), instanceOf(HelloMessage.class)); @@ -182,7 +182,7 @@ void shouldBeginTransactionWithoutBookmark() { Connection connection = connectionMock(protocol); CompletionStage stage = - protocol.beginTransaction(connection, Collections.emptySet(), TransactionConfig.empty(), null); + protocol.beginTransaction(connection, Collections.emptySet(), TransactionConfig.empty(), null, null); verify(connection) .writeAndFlush( @@ -192,6 +192,7 @@ void shouldBeginTransactionWithoutBookmark() { defaultDatabase(), WRITE, null, + null, null)), any(BeginTxResponseHandler.class)); assertNull(await(stage)); @@ -202,12 +203,13 @@ void shouldBeginTransactionWithBookmarks() { Connection connection = connectionMock(protocol); Set bookmarks = Collections.singleton(InternalBookmark.parse("neo4j:bookmark:v1:tx100")); - CompletionStage stage = protocol.beginTransaction(connection, bookmarks, TransactionConfig.empty(), null); + CompletionStage stage = + protocol.beginTransaction(connection, bookmarks, TransactionConfig.empty(), null, null); verify(connection) .writeAndFlush( eq(new BeginMessage( - bookmarks, TransactionConfig.empty(), defaultDatabase(), WRITE, null, null)), + bookmarks, TransactionConfig.empty(), defaultDatabase(), WRITE, null, null, null)), any(BeginTxResponseHandler.class)); assertNull(await(stage)); } @@ -216,11 +218,13 @@ void shouldBeginTransactionWithBookmarks() { void shouldBeginTransactionWithConfig() { Connection connection = connectionMock(protocol); - CompletionStage stage = protocol.beginTransaction(connection, Collections.emptySet(), txConfig, null); + CompletionStage stage = + protocol.beginTransaction(connection, Collections.emptySet(), txConfig, null, null); verify(connection) .writeAndFlush( - eq(new BeginMessage(Collections.emptySet(), txConfig, defaultDatabase(), WRITE, null, null)), + eq(new BeginMessage( + Collections.emptySet(), txConfig, defaultDatabase(), WRITE, null, null, null)), any(BeginTxResponseHandler.class)); assertNull(await(stage)); } @@ -230,11 +234,11 @@ void shouldBeginTransactionWithBookmarksAndConfig() { Connection connection = connectionMock(protocol); Set bookmarks = Collections.singleton(InternalBookmark.parse("neo4j:bookmark:v1:tx4242")); - CompletionStage stage = protocol.beginTransaction(connection, bookmarks, txConfig, null); + CompletionStage stage = protocol.beginTransaction(connection, bookmarks, txConfig, null, null); verify(connection) .writeAndFlush( - eq(new BeginMessage(bookmarks, txConfig, defaultDatabase(), WRITE, null, null)), + eq(new BeginMessage(bookmarks, txConfig, defaultDatabase(), WRITE, null, null, null)), any(BeginTxResponseHandler.class)); assertNull(await(stage)); } @@ -340,7 +344,7 @@ void databaseNameForAutoCommitTransactions() { @Test void shouldSupportDatabaseNameInBeginTransaction() { CompletionStage txStage = protocol.beginTransaction( - connectionMock("foo", protocol), Collections.emptySet(), TransactionConfig.empty(), null); + connectionMock("foo", protocol), Collections.emptySet(), TransactionConfig.empty(), null, null); assertDoesNotThrow(() -> await(txStage)); } @@ -353,7 +357,8 @@ void shouldNotSupportDatabaseNameForAutoCommitTransactions() { Collections.emptySet(), (ignored) -> {}, TransactionConfig.empty(), - UNLIMITED_FETCH_SIZE)); + UNLIMITED_FETCH_SIZE, + null)); } private Class expectedMessageFormatType() { @@ -368,7 +373,7 @@ private void testFailedRunInAutoCommitTxWithWaitingForResponse( Consumer bookmarkConsumer = mock(Consumer.class); CompletableFuture cursorFuture = protocol.runInAutoCommitTransaction( - connection, QUERY, bookmarks, bookmarkConsumer, config, UNLIMITED_FETCH_SIZE) + connection, QUERY, bookmarks, bookmarkConsumer, config, UNLIMITED_FETCH_SIZE, null) .asyncResult() .toCompletableFuture(); @@ -395,7 +400,7 @@ private void testSuccessfulRunInAutoCommitTxWithWaitingForResponse( Consumer bookmarkConsumer = mock(Consumer.class); CompletableFuture cursorFuture = protocol.runInAutoCommitTransaction( - connection, QUERY, bookmarks, bookmarkConsumer, config, UNLIMITED_FETCH_SIZE) + connection, QUERY, bookmarks, bookmarkConsumer, config, UNLIMITED_FETCH_SIZE, null) .asyncResult() .toCompletableFuture(); @@ -453,7 +458,7 @@ private void testRunAndWaitForRunResponse(boolean autoCommitTx, TransactionConfi @SuppressWarnings("unchecked") Consumer bookmarkConsumer = mock(Consumer.class); cursorStage = protocol.runInAutoCommitTransaction( - connection, QUERY, initialBookmarks, bookmarkConsumer, config, UNLIMITED_FETCH_SIZE) + connection, QUERY, initialBookmarks, bookmarkConsumer, config, UNLIMITED_FETCH_SIZE, null) .asyncResult(); } else { cursorStage = protocol.runInUnmanagedTransaction( @@ -483,7 +488,8 @@ private void testDatabaseNameSupport(boolean autoCommitTx) { Collections.emptySet(), (ignored) -> {}, TransactionConfig.empty(), - UNLIMITED_FETCH_SIZE); + UNLIMITED_FETCH_SIZE, + null); CompletionStage resultStage = factory.asyncResult(); ResponseHandler runHandler = verifySessionRunInvoked( connection, Collections.emptySet(), TransactionConfig.empty(), AccessMode.WRITE, database("foo")); @@ -492,8 +498,8 @@ private void testDatabaseNameSupport(boolean autoCommitTx) { verifySessionRunInvoked( connection, Collections.emptySet(), TransactionConfig.empty(), AccessMode.WRITE, database("foo")); } else { - CompletionStage txStage = - protocol.beginTransaction(connection, Collections.emptySet(), TransactionConfig.empty(), null); + CompletionStage txStage = protocol.beginTransaction( + connection, Collections.emptySet(), TransactionConfig.empty(), null, null); await(txStage); verifyBeginInvoked( connection, Collections.emptySet(), TransactionConfig.empty(), AccessMode.WRITE, database("foo")); @@ -511,7 +517,7 @@ private ResponseHandler verifySessionRunInvoked( AccessMode mode, DatabaseName databaseName) { RunWithMetadataMessage runMessage = - RunWithMetadataMessage.autoCommitTxRunMessage(QUERY, config, databaseName, mode, bookmarks, null); + RunWithMetadataMessage.autoCommitTxRunMessage(QUERY, config, databaseName, mode, bookmarks, null, null); return verifyRunInvoked(connection, runMessage); } @@ -535,7 +541,7 @@ private void verifyBeginInvoked( AccessMode mode, DatabaseName databaseName) { ArgumentCaptor beginHandlerCaptor = ArgumentCaptor.forClass(ResponseHandler.class); - BeginMessage beginMessage = new BeginMessage(bookmarks, config, databaseName, mode, null, null); + BeginMessage beginMessage = new BeginMessage(bookmarks, config, databaseName, mode, null, null, null); verify(connection).writeAndFlush(eq(beginMessage), beginHandlerCaptor.capture()); assertThat(beginHandlerCaptor.getValue(), instanceOf(BeginTxResponseHandler.class)); } diff --git a/driver/src/test/java/org/neo4j/driver/internal/messaging/v43/MessageWriterV43Test.java b/driver/src/test/java/org/neo4j/driver/internal/messaging/v43/MessageWriterV43Test.java index 8a1b5de966..0e8b2c5319 100644 --- a/driver/src/test/java/org/neo4j/driver/internal/messaging/v43/MessageWriterV43Test.java +++ b/driver/src/test/java/org/neo4j/driver/internal/messaging/v43/MessageWriterV43Test.java @@ -112,7 +112,8 @@ protected Stream supportedMessages() { "MyDriver/1.2.3", ((InternalAuthToken) basic("neo4j", "neo4j")).toMap(), Collections.emptyMap(), - false), + false, + null), GOODBYE, new BeginMessage( Collections.singleton(InternalBookmark.parse("neo4j:bookmark:v1:tx123")), @@ -121,6 +122,7 @@ protected Stream supportedMessages() { READ, defaultDatabase(), null, + null, null), new BeginMessage( Collections.singleton(InternalBookmark.parse("neo4j:bookmark:v1:tx123")), @@ -129,6 +131,7 @@ protected Stream supportedMessages() { WRITE, database("foo"), null, + null, null), COMMIT, ROLLBACK, @@ -140,6 +143,7 @@ protected Stream supportedMessages() { defaultDatabase(), READ, Collections.singleton(InternalBookmark.parse("neo4j:bookmark:v1:tx1")), + null, null), autoCommitTxRunMessage( new Query("RETURN 1"), @@ -148,6 +152,7 @@ protected Stream supportedMessages() { database("foo"), WRITE, Collections.singleton(InternalBookmark.parse("neo4j:bookmark:v1:tx1")), + null, null), unmanagedTxRunMessage(new Query("RETURN 1")), @@ -159,6 +164,7 @@ protected Stream supportedMessages() { defaultDatabase(), READ, Collections.emptySet(), + null, null), autoCommitTxRunMessage( new Query("RETURN $x", singletonMap("x", value(ZonedDateTime.now()))), @@ -167,6 +173,7 @@ protected Stream supportedMessages() { database("foo"), WRITE, Collections.emptySet(), + null, null), unmanagedTxRunMessage(new Query("RETURN $x", singletonMap("x", point(42, 1, 2, 3)))), diff --git a/driver/src/test/java/org/neo4j/driver/internal/messaging/v44/BoltProtocolV44Test.java b/driver/src/test/java/org/neo4j/driver/internal/messaging/v44/BoltProtocolV44Test.java index 43ddea119b..6f92adcc34 100644 --- a/driver/src/test/java/org/neo4j/driver/internal/messaging/v44/BoltProtocolV44Test.java +++ b/driver/src/test/java/org/neo4j/driver/internal/messaging/v44/BoltProtocolV44Test.java @@ -134,7 +134,7 @@ void shouldCreateMessageFormat() { void shouldInitializeChannel() { ChannelPromise promise = channel.newPromise(); - protocol.initializeChannel("MyDriver/0.0.1", dummyAuthToken(), RoutingContext.EMPTY, promise); + protocol.initializeChannel("MyDriver/0.0.1", dummyAuthToken(), RoutingContext.EMPTY, promise, null); assertThat(channel.outboundMessages(), hasSize(1)); assertThat(channel.outboundMessages().poll(), instanceOf(HelloMessage.class)); @@ -164,7 +164,7 @@ void shouldPrepareToCloseChannel() { void shouldFailToInitializeChannelWhenErrorIsReceived() { ChannelPromise promise = channel.newPromise(); - protocol.initializeChannel("MyDriver/2.2.1", dummyAuthToken(), RoutingContext.EMPTY, promise); + protocol.initializeChannel("MyDriver/2.2.1", dummyAuthToken(), RoutingContext.EMPTY, promise, null); assertThat(channel.outboundMessages(), hasSize(1)); assertThat(channel.outboundMessages().poll(), instanceOf(HelloMessage.class)); @@ -182,7 +182,7 @@ void shouldBeginTransactionWithoutBookmark() { Connection connection = connectionMock(protocol); CompletionStage stage = - protocol.beginTransaction(connection, Collections.emptySet(), TransactionConfig.empty(), null); + protocol.beginTransaction(connection, Collections.emptySet(), TransactionConfig.empty(), null, null); verify(connection) .writeAndFlush( @@ -192,6 +192,7 @@ void shouldBeginTransactionWithoutBookmark() { defaultDatabase(), WRITE, null, + null, null)), any(BeginTxResponseHandler.class)); assertNull(await(stage)); @@ -202,12 +203,13 @@ void shouldBeginTransactionWithBookmarks() { Connection connection = connectionMock(protocol); Set bookmarks = Collections.singleton(InternalBookmark.parse("neo4j:bookmark:v1:tx100")); - CompletionStage stage = protocol.beginTransaction(connection, bookmarks, TransactionConfig.empty(), null); + CompletionStage stage = + protocol.beginTransaction(connection, bookmarks, TransactionConfig.empty(), null, null); verify(connection) .writeAndFlush( eq(new BeginMessage( - bookmarks, TransactionConfig.empty(), defaultDatabase(), WRITE, null, null)), + bookmarks, TransactionConfig.empty(), defaultDatabase(), WRITE, null, null, null)), any(BeginTxResponseHandler.class)); assertNull(await(stage)); } @@ -216,11 +218,13 @@ void shouldBeginTransactionWithBookmarks() { void shouldBeginTransactionWithConfig() { Connection connection = connectionMock(protocol); - CompletionStage stage = protocol.beginTransaction(connection, Collections.emptySet(), txConfig, null); + CompletionStage stage = + protocol.beginTransaction(connection, Collections.emptySet(), txConfig, null, null); verify(connection) .writeAndFlush( - eq(new BeginMessage(Collections.emptySet(), txConfig, defaultDatabase(), WRITE, null, null)), + eq(new BeginMessage( + Collections.emptySet(), txConfig, defaultDatabase(), WRITE, null, null, null)), any(BeginTxResponseHandler.class)); assertNull(await(stage)); } @@ -230,11 +234,11 @@ void shouldBeginTransactionWithBookmarksAndConfig() { Connection connection = connectionMock(protocol); Set bookmarks = Collections.singleton(InternalBookmark.parse("neo4j:bookmark:v1:tx4242")); - CompletionStage stage = protocol.beginTransaction(connection, bookmarks, txConfig, null); + CompletionStage stage = protocol.beginTransaction(connection, bookmarks, txConfig, null, null); verify(connection) .writeAndFlush( - eq(new BeginMessage(bookmarks, txConfig, defaultDatabase(), WRITE, null, null)), + eq(new BeginMessage(bookmarks, txConfig, defaultDatabase(), WRITE, null, null, null)), any(BeginTxResponseHandler.class)); assertNull(await(stage)); } @@ -340,7 +344,7 @@ void databaseNameForAutoCommitTransactions() { @Test void shouldSupportDatabaseNameInBeginTransaction() { CompletionStage txStage = protocol.beginTransaction( - connectionMock("foo", protocol), Collections.emptySet(), TransactionConfig.empty(), null); + connectionMock("foo", protocol), Collections.emptySet(), TransactionConfig.empty(), null, null); assertDoesNotThrow(() -> await(txStage)); } @@ -353,7 +357,8 @@ void shouldNotSupportDatabaseNameForAutoCommitTransactions() { Collections.emptySet(), (ignored) -> {}, TransactionConfig.empty(), - UNLIMITED_FETCH_SIZE)); + UNLIMITED_FETCH_SIZE, + null)); } private Class expectedMessageFormatType() { @@ -368,7 +373,7 @@ private void testFailedRunInAutoCommitTxWithWaitingForResponse( Consumer bookmarkConsumer = mock(Consumer.class); CompletableFuture cursorFuture = protocol.runInAutoCommitTransaction( - connection, QUERY, bookmarks, bookmarkConsumer, config, UNLIMITED_FETCH_SIZE) + connection, QUERY, bookmarks, bookmarkConsumer, config, UNLIMITED_FETCH_SIZE, null) .asyncResult() .toCompletableFuture(); @@ -395,7 +400,7 @@ private void testSuccessfulRunInAutoCommitTxWithWaitingForResponse( Consumer bookmarkConsumer = mock(Consumer.class); CompletableFuture cursorFuture = protocol.runInAutoCommitTransaction( - connection, QUERY, bookmarks, bookmarkConsumer, config, UNLIMITED_FETCH_SIZE) + connection, QUERY, bookmarks, bookmarkConsumer, config, UNLIMITED_FETCH_SIZE, null) .asyncResult() .toCompletableFuture(); @@ -451,7 +456,7 @@ private void testRunAndWaitForRunResponse(boolean autoCommitTx, TransactionConfi CompletionStage cursorStage; if (autoCommitTx) { cursorStage = protocol.runInAutoCommitTransaction( - connection, QUERY, initialBookmarks, (ignored) -> {}, config, UNLIMITED_FETCH_SIZE) + connection, QUERY, initialBookmarks, (ignored) -> {}, config, UNLIMITED_FETCH_SIZE, null) .asyncResult(); } else { cursorStage = protocol.runInUnmanagedTransaction( @@ -481,7 +486,8 @@ private void testDatabaseNameSupport(boolean autoCommitTx) { Collections.emptySet(), (ignored) -> {}, TransactionConfig.empty(), - UNLIMITED_FETCH_SIZE); + UNLIMITED_FETCH_SIZE, + null); CompletionStage resultStage = factory.asyncResult(); ResponseHandler runHandler = verifySessionRunInvoked( connection, Collections.emptySet(), TransactionConfig.empty(), AccessMode.WRITE, database("foo")); @@ -490,8 +496,8 @@ private void testDatabaseNameSupport(boolean autoCommitTx) { verifySessionRunInvoked( connection, Collections.emptySet(), TransactionConfig.empty(), AccessMode.WRITE, database("foo")); } else { - CompletionStage txStage = - protocol.beginTransaction(connection, Collections.emptySet(), TransactionConfig.empty(), null); + CompletionStage txStage = protocol.beginTransaction( + connection, Collections.emptySet(), TransactionConfig.empty(), null, null); await(txStage); verifyBeginInvoked( connection, Collections.emptySet(), TransactionConfig.empty(), AccessMode.WRITE, database("foo")); @@ -509,7 +515,7 @@ private ResponseHandler verifySessionRunInvoked( AccessMode mode, DatabaseName databaseName) { RunWithMetadataMessage runMessage = - RunWithMetadataMessage.autoCommitTxRunMessage(QUERY, config, databaseName, mode, bookmarks, null); + RunWithMetadataMessage.autoCommitTxRunMessage(QUERY, config, databaseName, mode, bookmarks, null, null); return verifyRunInvoked(connection, runMessage); } @@ -533,7 +539,7 @@ private void verifyBeginInvoked( AccessMode mode, DatabaseName databaseName) { ArgumentCaptor beginHandlerCaptor = ArgumentCaptor.forClass(ResponseHandler.class); - BeginMessage beginMessage = new BeginMessage(bookmarks, config, databaseName, mode, null, null); + BeginMessage beginMessage = new BeginMessage(bookmarks, config, databaseName, mode, null, null, null); verify(connection).writeAndFlush(eq(beginMessage), beginHandlerCaptor.capture()); assertThat(beginHandlerCaptor.getValue(), instanceOf(BeginTxResponseHandler.class)); } diff --git a/driver/src/test/java/org/neo4j/driver/internal/messaging/v44/MessageWriterV44Test.java b/driver/src/test/java/org/neo4j/driver/internal/messaging/v44/MessageWriterV44Test.java index 0ac43c80fc..9734e81ea4 100644 --- a/driver/src/test/java/org/neo4j/driver/internal/messaging/v44/MessageWriterV44Test.java +++ b/driver/src/test/java/org/neo4j/driver/internal/messaging/v44/MessageWriterV44Test.java @@ -112,7 +112,8 @@ protected Stream supportedMessages() { "MyDriver/1.2.3", ((InternalAuthToken) basic("neo4j", "neo4j")).toMap(), Collections.emptyMap(), - false), + false, + null), GOODBYE, new BeginMessage( Collections.singleton(InternalBookmark.parse("neo4j:bookmark:v1:tx123")), @@ -121,6 +122,7 @@ protected Stream supportedMessages() { READ, defaultDatabase(), null, + null, null), new BeginMessage( Collections.singleton(InternalBookmark.parse("neo4j:bookmark:v1:tx123")), @@ -129,6 +131,7 @@ protected Stream supportedMessages() { WRITE, database("foo"), null, + null, null), COMMIT, ROLLBACK, @@ -140,6 +143,7 @@ protected Stream supportedMessages() { defaultDatabase(), READ, Collections.singleton(InternalBookmark.parse("neo4j:bookmark:v1:tx1")), + null, null), autoCommitTxRunMessage( new Query("RETURN 1"), @@ -148,6 +152,7 @@ protected Stream supportedMessages() { database("foo"), WRITE, Collections.singleton(InternalBookmark.parse("neo4j:bookmark:v1:tx1")), + null, null), unmanagedTxRunMessage(new Query("RETURN 1")), @@ -159,6 +164,7 @@ protected Stream supportedMessages() { defaultDatabase(), READ, Collections.emptySet(), + null, null), autoCommitTxRunMessage( new Query("RETURN $x", singletonMap("x", value(ZonedDateTime.now()))), @@ -167,6 +173,7 @@ protected Stream supportedMessages() { database("foo"), WRITE, Collections.emptySet(), + null, null), unmanagedTxRunMessage(new Query("RETURN $x", singletonMap("x", point(42, 1, 2, 3)))), diff --git a/driver/src/test/java/org/neo4j/driver/internal/messaging/v5/BoltProtocolV5Test.java b/driver/src/test/java/org/neo4j/driver/internal/messaging/v5/BoltProtocolV5Test.java index cecfcc043e..1c7836b6fd 100644 --- a/driver/src/test/java/org/neo4j/driver/internal/messaging/v5/BoltProtocolV5Test.java +++ b/driver/src/test/java/org/neo4j/driver/internal/messaging/v5/BoltProtocolV5Test.java @@ -134,7 +134,7 @@ void shouldCreateMessageFormat() { void shouldInitializeChannel() { ChannelPromise promise = channel.newPromise(); - protocol.initializeChannel("MyDriver/0.0.1", dummyAuthToken(), RoutingContext.EMPTY, promise); + protocol.initializeChannel("MyDriver/0.0.1", dummyAuthToken(), RoutingContext.EMPTY, promise, null); assertThat(channel.outboundMessages(), hasSize(1)); assertThat(channel.outboundMessages().poll(), instanceOf(HelloMessage.class)); @@ -164,7 +164,7 @@ void shouldPrepareToCloseChannel() { void shouldFailToInitializeChannelWhenErrorIsReceived() { ChannelPromise promise = channel.newPromise(); - protocol.initializeChannel("MyDriver/2.2.1", dummyAuthToken(), RoutingContext.EMPTY, promise); + protocol.initializeChannel("MyDriver/2.2.1", dummyAuthToken(), RoutingContext.EMPTY, promise, null); assertThat(channel.outboundMessages(), hasSize(1)); assertThat(channel.outboundMessages().poll(), instanceOf(HelloMessage.class)); @@ -182,7 +182,7 @@ void shouldBeginTransactionWithoutBookmark() { Connection connection = connectionMock(protocol); CompletionStage stage = - protocol.beginTransaction(connection, Collections.emptySet(), TransactionConfig.empty(), null); + protocol.beginTransaction(connection, Collections.emptySet(), TransactionConfig.empty(), null, null); verify(connection) .writeAndFlush( @@ -192,6 +192,7 @@ void shouldBeginTransactionWithoutBookmark() { defaultDatabase(), WRITE, null, + null, null)), any(BeginTxResponseHandler.class)); assertNull(await(stage)); @@ -202,12 +203,13 @@ void shouldBeginTransactionWithBookmarks() { Connection connection = connectionMock(protocol); Set bookmarks = Collections.singleton(InternalBookmark.parse("neo4j:bookmark:v1:tx100")); - CompletionStage stage = protocol.beginTransaction(connection, bookmarks, TransactionConfig.empty(), null); + CompletionStage stage = + protocol.beginTransaction(connection, bookmarks, TransactionConfig.empty(), null, null); verify(connection) .writeAndFlush( eq(new BeginMessage( - bookmarks, TransactionConfig.empty(), defaultDatabase(), WRITE, null, null)), + bookmarks, TransactionConfig.empty(), defaultDatabase(), WRITE, null, null, null)), any(BeginTxResponseHandler.class)); assertNull(await(stage)); } @@ -216,11 +218,13 @@ void shouldBeginTransactionWithBookmarks() { void shouldBeginTransactionWithConfig() { Connection connection = connectionMock(protocol); - CompletionStage stage = protocol.beginTransaction(connection, Collections.emptySet(), txConfig, null); + CompletionStage stage = + protocol.beginTransaction(connection, Collections.emptySet(), txConfig, null, null); verify(connection) .writeAndFlush( - eq(new BeginMessage(Collections.emptySet(), txConfig, defaultDatabase(), WRITE, null, null)), + eq(new BeginMessage( + Collections.emptySet(), txConfig, defaultDatabase(), WRITE, null, null, null)), any(BeginTxResponseHandler.class)); assertNull(await(stage)); } @@ -230,11 +234,11 @@ void shouldBeginTransactionWithBookmarksAndConfig() { Connection connection = connectionMock(protocol); Set bookmarks = Collections.singleton(InternalBookmark.parse("neo4j:bookmark:v1:tx4242")); - CompletionStage stage = protocol.beginTransaction(connection, bookmarks, txConfig, null); + CompletionStage stage = protocol.beginTransaction(connection, bookmarks, txConfig, null, null); verify(connection) .writeAndFlush( - eq(new BeginMessage(bookmarks, txConfig, defaultDatabase(), WRITE, null, null)), + eq(new BeginMessage(bookmarks, txConfig, defaultDatabase(), WRITE, null, null, null)), any(BeginTxResponseHandler.class)); assertNull(await(stage)); } @@ -340,7 +344,7 @@ void databaseNameForAutoCommitTransactions() { @Test void shouldSupportDatabaseNameInBeginTransaction() { CompletionStage txStage = protocol.beginTransaction( - connectionMock("foo", protocol), Collections.emptySet(), TransactionConfig.empty(), null); + connectionMock("foo", protocol), Collections.emptySet(), TransactionConfig.empty(), null, null); assertDoesNotThrow(() -> await(txStage)); } @@ -353,7 +357,8 @@ void shouldNotSupportDatabaseNameForAutoCommitTransactions() { Collections.emptySet(), (ignored) -> {}, TransactionConfig.empty(), - UNLIMITED_FETCH_SIZE)); + UNLIMITED_FETCH_SIZE, + null)); } private Class expectedMessageFormatType() { @@ -368,7 +373,7 @@ private void testFailedRunInAutoCommitTxWithWaitingForResponse( Consumer bookmarkConsumer = mock(Consumer.class); CompletableFuture cursorFuture = protocol.runInAutoCommitTransaction( - connection, QUERY, bookmarks, bookmarkConsumer, config, UNLIMITED_FETCH_SIZE) + connection, QUERY, bookmarks, bookmarkConsumer, config, UNLIMITED_FETCH_SIZE, null) .asyncResult() .toCompletableFuture(); @@ -395,7 +400,7 @@ private void testSuccessfulRunInAutoCommitTxWithWaitingForResponse( Consumer bookmarkConsumer = mock(Consumer.class); CompletableFuture cursorFuture = protocol.runInAutoCommitTransaction( - connection, QUERY, bookmarks, bookmarkConsumer, config, UNLIMITED_FETCH_SIZE) + connection, QUERY, bookmarks, bookmarkConsumer, config, UNLIMITED_FETCH_SIZE, null) .asyncResult() .toCompletableFuture(); @@ -451,7 +456,7 @@ private void testRunAndWaitForRunResponse(boolean autoCommitTx, TransactionConfi CompletionStage cursorStage; if (autoCommitTx) { cursorStage = protocol.runInAutoCommitTransaction( - connection, QUERY, initialBookmarks, (ignored) -> {}, config, UNLIMITED_FETCH_SIZE) + connection, QUERY, initialBookmarks, (ignored) -> {}, config, UNLIMITED_FETCH_SIZE, null) .asyncResult(); } else { cursorStage = protocol.runInUnmanagedTransaction( @@ -481,7 +486,8 @@ private void testDatabaseNameSupport(boolean autoCommitTx) { Collections.emptySet(), (ignored) -> {}, TransactionConfig.empty(), - UNLIMITED_FETCH_SIZE); + UNLIMITED_FETCH_SIZE, + null); CompletionStage resultStage = factory.asyncResult(); ResponseHandler runHandler = verifySessionRunInvoked( connection, Collections.emptySet(), TransactionConfig.empty(), AccessMode.WRITE, database("foo")); @@ -490,8 +496,8 @@ private void testDatabaseNameSupport(boolean autoCommitTx) { verifySessionRunInvoked( connection, Collections.emptySet(), TransactionConfig.empty(), AccessMode.WRITE, database("foo")); } else { - CompletionStage txStage = - protocol.beginTransaction(connection, Collections.emptySet(), TransactionConfig.empty(), null); + CompletionStage txStage = protocol.beginTransaction( + connection, Collections.emptySet(), TransactionConfig.empty(), null, null); await(txStage); verifyBeginInvoked( connection, Collections.emptySet(), TransactionConfig.empty(), AccessMode.WRITE, database("foo")); @@ -509,7 +515,7 @@ private ResponseHandler verifySessionRunInvoked( AccessMode mode, DatabaseName databaseName) { RunWithMetadataMessage runMessage = - RunWithMetadataMessage.autoCommitTxRunMessage(QUERY, config, databaseName, mode, bookmark, null); + RunWithMetadataMessage.autoCommitTxRunMessage(QUERY, config, databaseName, mode, bookmark, null, null); return verifyRunInvoked(connection, runMessage); } @@ -533,7 +539,7 @@ private void verifyBeginInvoked( AccessMode mode, DatabaseName databaseName) { ArgumentCaptor beginHandlerCaptor = ArgumentCaptor.forClass(ResponseHandler.class); - BeginMessage beginMessage = new BeginMessage(bookmarks, config, databaseName, mode, null, null); + BeginMessage beginMessage = new BeginMessage(bookmarks, config, databaseName, mode, null, null, null); verify(connection).writeAndFlush(eq(beginMessage), beginHandlerCaptor.capture()); assertThat(beginHandlerCaptor.getValue(), instanceOf(BeginTxResponseHandler.class)); } diff --git a/driver/src/test/java/org/neo4j/driver/internal/messaging/v5/MessageWriterV5Test.java b/driver/src/test/java/org/neo4j/driver/internal/messaging/v5/MessageWriterV5Test.java index 92d3ffb7da..fa4b12be08 100644 --- a/driver/src/test/java/org/neo4j/driver/internal/messaging/v5/MessageWriterV5Test.java +++ b/driver/src/test/java/org/neo4j/driver/internal/messaging/v5/MessageWriterV5Test.java @@ -112,7 +112,8 @@ protected Stream supportedMessages() { "MyDriver/1.2.3", ((InternalAuthToken) basic("neo4j", "neo4j")).toMap(), Collections.emptyMap(), - false), + false, + null), GOODBYE, new BeginMessage( Collections.singleton(InternalBookmark.parse("neo4j:bookmark:v1:tx123")), @@ -121,6 +122,7 @@ protected Stream supportedMessages() { READ, defaultDatabase(), null, + null, null), new BeginMessage( Collections.singleton(InternalBookmark.parse("neo4j:bookmark:v1:tx123")), @@ -129,6 +131,7 @@ protected Stream supportedMessages() { WRITE, database("foo"), null, + null, null), COMMIT, ROLLBACK, @@ -140,6 +143,7 @@ protected Stream supportedMessages() { defaultDatabase(), READ, Collections.singleton(InternalBookmark.parse("neo4j:bookmark:v1:tx1")), + null, null), autoCommitTxRunMessage( new Query("RETURN 1"), @@ -148,6 +152,7 @@ protected Stream supportedMessages() { database("foo"), WRITE, Collections.singleton(InternalBookmark.parse("neo4j:bookmark:v1:tx1")), + null, null), unmanagedTxRunMessage(new Query("RETURN 1")), @@ -159,6 +164,7 @@ protected Stream supportedMessages() { defaultDatabase(), READ, Collections.emptySet(), + null, null), autoCommitTxRunMessage( new Query("RETURN $x", singletonMap("x", value(ZonedDateTime.now()))), @@ -167,6 +173,7 @@ protected Stream supportedMessages() { database("foo"), WRITE, Collections.emptySet(), + null, null), unmanagedTxRunMessage(new Query("RETURN $x", singletonMap("x", point(42, 1, 2, 3)))), diff --git a/driver/src/test/java/org/neo4j/driver/internal/messaging/v51/BoltProtocolV51Test.java b/driver/src/test/java/org/neo4j/driver/internal/messaging/v51/BoltProtocolV51Test.java new file mode 100644 index 0000000000..d543f4acfb --- /dev/null +++ b/driver/src/test/java/org/neo4j/driver/internal/messaging/v51/BoltProtocolV51Test.java @@ -0,0 +1,533 @@ +/* + * Copyright (c) "Neo4j" + * Neo4j Sweden AB [http://neo4j.com] + * + * This file is part of Neo4j. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.neo4j.driver.internal.messaging.v51; + +import static java.time.Duration.ofSeconds; +import static java.util.Collections.emptyMap; +import static java.util.Collections.singletonMap; +import static org.hamcrest.Matchers.hasSize; +import static org.hamcrest.Matchers.instanceOf; +import static org.hamcrest.junit.MatcherAssert.assertThat; +import static org.junit.jupiter.api.Assertions.assertDoesNotThrow; +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertFalse; +import static org.junit.jupiter.api.Assertions.assertNotNull; +import static org.junit.jupiter.api.Assertions.assertNull; +import static org.junit.jupiter.api.Assertions.assertSame; +import static org.junit.jupiter.api.Assertions.assertThrows; +import static org.junit.jupiter.api.Assertions.assertTrue; +import static org.mockito.ArgumentMatchers.any; +import static org.mockito.ArgumentMatchers.eq; +import static org.mockito.BDDMockito.then; +import static org.mockito.Mockito.doAnswer; +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.times; +import static org.mockito.Mockito.verify; +import static org.mockito.Mockito.when; +import static org.neo4j.driver.AccessMode.WRITE; +import static org.neo4j.driver.Values.value; +import static org.neo4j.driver.internal.DatabaseNameUtil.database; +import static org.neo4j.driver.internal.DatabaseNameUtil.defaultDatabase; +import static org.neo4j.driver.internal.handlers.pulln.FetchSizeUtil.UNLIMITED_FETCH_SIZE; +import static org.neo4j.driver.testutil.TestUtil.await; +import static org.neo4j.driver.testutil.TestUtil.connectionMock; + +import io.netty.channel.ChannelPromise; +import io.netty.channel.embedded.EmbeddedChannel; +import java.util.Collections; +import java.util.HashMap; +import java.util.Map; +import java.util.Set; +import java.util.concurrent.CompletableFuture; +import java.util.concurrent.CompletionStage; +import java.util.function.Consumer; +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.ArgumentCaptor; +import org.neo4j.driver.AccessMode; +import org.neo4j.driver.AuthTokens; +import org.neo4j.driver.Bookmark; +import org.neo4j.driver.Logging; +import org.neo4j.driver.Query; +import org.neo4j.driver.TransactionConfig; +import org.neo4j.driver.Value; +import org.neo4j.driver.internal.DatabaseBookmark; +import org.neo4j.driver.internal.DatabaseName; +import org.neo4j.driver.internal.InternalBookmark; +import org.neo4j.driver.internal.async.UnmanagedTransaction; +import org.neo4j.driver.internal.async.connection.ChannelAttributes; +import org.neo4j.driver.internal.async.inbound.InboundMessageDispatcher; +import org.neo4j.driver.internal.cluster.RoutingContext; +import org.neo4j.driver.internal.cursor.AsyncResultCursor; +import org.neo4j.driver.internal.cursor.ResultCursorFactory; +import org.neo4j.driver.internal.handlers.BeginTxResponseHandler; +import org.neo4j.driver.internal.handlers.CommitTxResponseHandler; +import org.neo4j.driver.internal.handlers.PullAllResponseHandler; +import org.neo4j.driver.internal.handlers.RollbackTxResponseHandler; +import org.neo4j.driver.internal.handlers.RunResponseHandler; +import org.neo4j.driver.internal.messaging.BoltProtocol; +import org.neo4j.driver.internal.messaging.MessageFormat; +import org.neo4j.driver.internal.messaging.request.BeginMessage; +import org.neo4j.driver.internal.messaging.request.CommitMessage; +import org.neo4j.driver.internal.messaging.request.GoodbyeMessage; +import org.neo4j.driver.internal.messaging.request.PullMessage; +import org.neo4j.driver.internal.messaging.request.RollbackMessage; +import org.neo4j.driver.internal.messaging.request.RunWithMetadataMessage; +import org.neo4j.driver.internal.security.InternalAuthToken; +import org.neo4j.driver.internal.spi.Connection; +import org.neo4j.driver.internal.spi.ResponseHandler; + +public class BoltProtocolV51Test { + protected static final String QUERY_TEXT = "RETURN $x"; + protected static final Map PARAMS = singletonMap("x", value(42)); + protected static final Query QUERY = new Query(QUERY_TEXT, value(PARAMS)); + + protected final BoltProtocol protocol = createProtocol(); + private final EmbeddedChannel channel = new EmbeddedChannel(); + private final InboundMessageDispatcher messageDispatcher = new InboundMessageDispatcher(channel, Logging.none()); + + private final TransactionConfig txConfig = TransactionConfig.builder() + .withTimeout(ofSeconds(12)) + .withMetadata(singletonMap("key", value(42))) + .build(); + + protected BoltProtocol createProtocol() { + return BoltProtocolV51.INSTANCE; + } + + @BeforeEach + void beforeEach() { + ChannelAttributes.setMessageDispatcher(channel, messageDispatcher); + } + + @AfterEach + void afterEach() { + channel.finishAndReleaseAll(); + } + + @Test + void shouldCreateMessageFormat() { + assertThat(protocol.createMessageFormat(), instanceOf(expectedMessageFormatType())); + } + + @Test + void shouldInitializeChannel() { + ChannelPromise promise = channel.newPromise(); + + protocol.initializeChannel("MyDriver/0.0.1", dummyAuthToken(), RoutingContext.EMPTY, promise, null); + + assertThat(channel.outboundMessages(), hasSize(2)); + assertEquals(2, messageDispatcher.queuedHandlersCount()); + assertFalse(promise.isDone()); + + Map metadata = new HashMap<>(); + metadata.put("server", value("Neo4j/4.4.0")); + metadata.put("connection_id", value("bolt-42")); + + messageDispatcher.handleSuccessMessage(metadata); + messageDispatcher.handleSuccessMessage(Map.of()); + + channel.flush(); + assertTrue(promise.isDone()); + assertTrue(promise.isSuccess()); + } + + @Test + void shouldPrepareToCloseChannel() { + protocol.prepareToCloseChannel(channel); + + assertThat(channel.outboundMessages(), hasSize(1)); + assertThat(channel.outboundMessages().poll(), instanceOf(GoodbyeMessage.class)); + assertEquals(1, messageDispatcher.queuedHandlersCount()); + } + + @Test + void shouldBeginTransactionWithoutBookmark() { + Connection connection = connectionMock(protocol); + + CompletionStage stage = + protocol.beginTransaction(connection, Collections.emptySet(), TransactionConfig.empty(), null, null); + + verify(connection) + .writeAndFlush( + eq(new BeginMessage( + Collections.emptySet(), + TransactionConfig.empty(), + defaultDatabase(), + WRITE, + null, + null, + null)), + any(BeginTxResponseHandler.class)); + assertNull(await(stage)); + } + + @Test + void shouldBeginTransactionWithBookmarks() { + Connection connection = connectionMock(protocol); + Set bookmarks = Collections.singleton(InternalBookmark.parse("neo4j:bookmark:v1:tx100")); + + CompletionStage stage = + protocol.beginTransaction(connection, bookmarks, TransactionConfig.empty(), null, null); + + verify(connection) + .writeAndFlush( + eq(new BeginMessage( + bookmarks, TransactionConfig.empty(), defaultDatabase(), WRITE, null, null, null)), + any(BeginTxResponseHandler.class)); + assertNull(await(stage)); + } + + @Test + void shouldBeginTransactionWithConfig() { + Connection connection = connectionMock(protocol); + + CompletionStage stage = + protocol.beginTransaction(connection, Collections.emptySet(), txConfig, null, null); + + verify(connection) + .writeAndFlush( + eq(new BeginMessage( + Collections.emptySet(), txConfig, defaultDatabase(), WRITE, null, null, null)), + any(BeginTxResponseHandler.class)); + assertNull(await(stage)); + } + + @Test + void shouldBeginTransactionWithBookmarksAndConfig() { + Connection connection = connectionMock(protocol); + Set bookmarks = Collections.singleton(InternalBookmark.parse("neo4j:bookmark:v1:tx4242")); + + CompletionStage stage = protocol.beginTransaction(connection, bookmarks, txConfig, null, null); + + verify(connection) + .writeAndFlush( + eq(new BeginMessage(bookmarks, txConfig, defaultDatabase(), WRITE, null, null, null)), + any(BeginTxResponseHandler.class)); + assertNull(await(stage)); + } + + @Test + void shouldCommitTransaction() { + String bookmarkString = "neo4j:bookmark:v1:tx4242"; + + Connection connection = connectionMock(protocol); + when(connection.protocol()).thenReturn(protocol); + doAnswer(invocation -> { + ResponseHandler commitHandler = invocation.getArgument(1); + commitHandler.onSuccess(singletonMap("bookmark", value(bookmarkString))); + return null; + }) + .when(connection) + .writeAndFlush(eq(CommitMessage.COMMIT), any()); + + CompletionStage stage = protocol.commitTransaction(connection); + + verify(connection).writeAndFlush(eq(CommitMessage.COMMIT), any(CommitTxResponseHandler.class)); + assertEquals(InternalBookmark.parse(bookmarkString), await(stage).bookmark()); + } + + @Test + void shouldRollbackTransaction() { + Connection connection = connectionMock(protocol); + + CompletionStage stage = protocol.rollbackTransaction(connection); + + verify(connection).writeAndFlush(eq(RollbackMessage.ROLLBACK), any(RollbackTxResponseHandler.class)); + assertNull(await(stage)); + } + + @ParameterizedTest + @EnumSource(AccessMode.class) + void shouldRunInAutoCommitTransactionAndWaitForRunResponse(AccessMode mode) throws Exception { + testRunAndWaitForRunResponse(true, TransactionConfig.empty(), mode); + } + + @ParameterizedTest + @EnumSource(AccessMode.class) + void shouldRunInAutoCommitWithConfigTransactionAndWaitForRunResponse(AccessMode mode) throws Exception { + testRunAndWaitForRunResponse(true, txConfig, mode); + } + + @ParameterizedTest + @EnumSource(AccessMode.class) + void shouldRunInAutoCommitTransactionAndWaitForSuccessRunResponse(AccessMode mode) throws Exception { + testSuccessfulRunInAutoCommitTxWithWaitingForResponse(Collections.emptySet(), TransactionConfig.empty(), mode); + } + + @ParameterizedTest + @EnumSource(AccessMode.class) + void shouldRunInAutoCommitTransactionWithBookmarkAndConfigAndWaitForSuccessRunResponse(AccessMode mode) + throws Exception { + testSuccessfulRunInAutoCommitTxWithWaitingForResponse( + Collections.singleton(InternalBookmark.parse("neo4j:bookmark:v1:tx65")), txConfig, mode); + } + + @ParameterizedTest + @EnumSource(AccessMode.class) + void shouldRunInAutoCommitTransactionAndWaitForFailureRunResponse(AccessMode mode) throws Exception { + testFailedRunInAutoCommitTxWithWaitingForResponse(Collections.emptySet(), TransactionConfig.empty(), mode); + } + + @ParameterizedTest + @EnumSource(AccessMode.class) + void shouldRunInAutoCommitTransactionWithBookmarkAndConfigAndWaitForFailureRunResponse(AccessMode mode) + throws Exception { + testFailedRunInAutoCommitTxWithWaitingForResponse( + Collections.singleton(InternalBookmark.parse("neo4j:bookmark:v1:tx163")), txConfig, mode); + } + + @ParameterizedTest + @EnumSource(AccessMode.class) + void shouldRunInUnmanagedTransactionAndWaitForRunResponse(AccessMode mode) throws Exception { + testRunAndWaitForRunResponse(false, TransactionConfig.empty(), mode); + } + + @ParameterizedTest + @EnumSource(AccessMode.class) + void shouldRunInUnmanagedTransactionAndWaitForSuccessRunResponse(AccessMode mode) throws Exception { + testRunInUnmanagedTransactionAndWaitForRunResponse(true, mode); + } + + @ParameterizedTest + @EnumSource(AccessMode.class) + void shouldRunInUnmanagedTransactionAndWaitForFailureRunResponse(AccessMode mode) throws Exception { + testRunInUnmanagedTransactionAndWaitForRunResponse(false, mode); + } + + @Test + void databaseNameInBeginTransaction() { + testDatabaseNameSupport(false); + } + + @Test + void databaseNameForAutoCommitTransactions() { + testDatabaseNameSupport(true); + } + + @Test + void shouldSupportDatabaseNameInBeginTransaction() { + CompletionStage txStage = protocol.beginTransaction( + connectionMock("foo", protocol), Collections.emptySet(), TransactionConfig.empty(), null, null); + + assertDoesNotThrow(() -> await(txStage)); + } + + @Test + void shouldNotSupportDatabaseNameForAutoCommitTransactions() { + assertDoesNotThrow(() -> protocol.runInAutoCommitTransaction( + connectionMock("foo", protocol), + new Query("RETURN 1"), + Collections.emptySet(), + (ignored) -> {}, + TransactionConfig.empty(), + UNLIMITED_FETCH_SIZE, + null)); + } + + private Class expectedMessageFormatType() { + return MessageFormatV51.class; + } + + private void testFailedRunInAutoCommitTxWithWaitingForResponse( + Set bookmarks, TransactionConfig config, AccessMode mode) throws Exception { + // Given + Connection connection = connectionMock(mode, protocol); + @SuppressWarnings("unchecked") + Consumer bookmarkConsumer = mock(Consumer.class); + + CompletableFuture cursorFuture = protocol.runInAutoCommitTransaction( + connection, QUERY, bookmarks, bookmarkConsumer, config, UNLIMITED_FETCH_SIZE, null) + .asyncResult() + .toCompletableFuture(); + + ResponseHandler runHandler = verifySessionRunInvoked(connection, bookmarks, config, mode, defaultDatabase()); + assertFalse(cursorFuture.isDone()); + + // When I response to Run message with a failure + Throwable error = new RuntimeException(); + runHandler.onFailure(error); + + // Then + then(bookmarkConsumer).should(times(0)).accept(any()); + assertTrue(cursorFuture.isDone()); + Throwable actual = + assertThrows(error.getClass(), () -> await(cursorFuture.get().mapSuccessfulRunCompletionAsync())); + assertSame(error, actual); + } + + private void testSuccessfulRunInAutoCommitTxWithWaitingForResponse( + Set bookmarks, TransactionConfig config, AccessMode mode) throws Exception { + // Given + Connection connection = connectionMock(mode, protocol); + @SuppressWarnings("unchecked") + Consumer bookmarkConsumer = mock(Consumer.class); + + CompletableFuture cursorFuture = protocol.runInAutoCommitTransaction( + connection, QUERY, bookmarks, bookmarkConsumer, config, UNLIMITED_FETCH_SIZE, null) + .asyncResult() + .toCompletableFuture(); + + ResponseHandler runHandler = verifySessionRunInvoked(connection, bookmarks, config, mode, defaultDatabase()); + assertFalse(cursorFuture.isDone()); + + // When I response to the run message + runHandler.onSuccess(emptyMap()); + + // Then + then(bookmarkConsumer).should(times(0)).accept(any()); + assertTrue(cursorFuture.isDone()); + assertNotNull(cursorFuture.get()); + } + + private void testRunInUnmanagedTransactionAndWaitForRunResponse(boolean success, AccessMode mode) throws Exception { + // Given + Connection connection = connectionMock(mode, protocol); + + CompletableFuture cursorFuture = protocol.runInUnmanagedTransaction( + connection, QUERY, mock(UnmanagedTransaction.class), UNLIMITED_FETCH_SIZE) + .asyncResult() + .toCompletableFuture(); + + ResponseHandler runHandler = verifyTxRunInvoked(connection); + assertFalse(cursorFuture.isDone()); + Throwable error = new RuntimeException(); + + if (success) { + runHandler.onSuccess(emptyMap()); + } else { + // When responded with a failure + runHandler.onFailure(error); + } + + // Then + assertTrue(cursorFuture.isDone()); + if (success) { + assertNotNull(await(cursorFuture.get().mapSuccessfulRunCompletionAsync())); + } else { + Throwable actual = assertThrows( + error.getClass(), () -> await(cursorFuture.get().mapSuccessfulRunCompletionAsync())); + assertSame(error, actual); + } + } + + private void testRunAndWaitForRunResponse(boolean autoCommitTx, TransactionConfig config, AccessMode mode) + throws Exception { + // Given + Connection connection = connectionMock(mode, protocol); + Set initialBookmarks = Collections.singleton(InternalBookmark.parse("neo4j:bookmark:v1:tx987")); + + CompletionStage cursorStage; + if (autoCommitTx) { + cursorStage = protocol.runInAutoCommitTransaction( + connection, QUERY, initialBookmarks, (ignored) -> {}, config, UNLIMITED_FETCH_SIZE, null) + .asyncResult(); + } else { + cursorStage = protocol.runInUnmanagedTransaction( + connection, QUERY, mock(UnmanagedTransaction.class), UNLIMITED_FETCH_SIZE) + .asyncResult(); + } + + // When & Then + CompletableFuture cursorFuture = cursorStage.toCompletableFuture(); + assertFalse(cursorFuture.isDone()); + + ResponseHandler runResponseHandler = autoCommitTx + ? verifySessionRunInvoked(connection, initialBookmarks, config, mode, defaultDatabase()) + : verifyTxRunInvoked(connection); + runResponseHandler.onSuccess(emptyMap()); + + assertTrue(cursorFuture.isDone()); + assertNotNull(cursorFuture.get()); + } + + private void testDatabaseNameSupport(boolean autoCommitTx) { + Connection connection = connectionMock("foo", protocol); + if (autoCommitTx) { + ResultCursorFactory factory = protocol.runInAutoCommitTransaction( + connection, + QUERY, + Collections.emptySet(), + (ignored) -> {}, + TransactionConfig.empty(), + UNLIMITED_FETCH_SIZE, + null); + CompletionStage resultStage = factory.asyncResult(); + ResponseHandler runHandler = verifySessionRunInvoked( + connection, Collections.emptySet(), TransactionConfig.empty(), AccessMode.WRITE, database("foo")); + runHandler.onSuccess(emptyMap()); + await(resultStage); + verifySessionRunInvoked( + connection, Collections.emptySet(), TransactionConfig.empty(), AccessMode.WRITE, database("foo")); + } else { + CompletionStage txStage = protocol.beginTransaction( + connection, Collections.emptySet(), TransactionConfig.empty(), null, null); + await(txStage); + verifyBeginInvoked( + connection, Collections.emptySet(), TransactionConfig.empty(), AccessMode.WRITE, database("foo")); + } + } + + private ResponseHandler verifyTxRunInvoked(Connection connection) { + return verifyRunInvoked(connection, RunWithMetadataMessage.unmanagedTxRunMessage(QUERY)); + } + + private ResponseHandler verifySessionRunInvoked( + Connection connection, + Set bookmark, + TransactionConfig config, + AccessMode mode, + DatabaseName databaseName) { + RunWithMetadataMessage runMessage = + RunWithMetadataMessage.autoCommitTxRunMessage(QUERY, config, databaseName, mode, bookmark, null, null); + return verifyRunInvoked(connection, runMessage); + } + + private ResponseHandler verifyRunInvoked(Connection connection, RunWithMetadataMessage runMessage) { + ArgumentCaptor runHandlerCaptor = ArgumentCaptor.forClass(ResponseHandler.class); + ArgumentCaptor pullHandlerCaptor = ArgumentCaptor.forClass(ResponseHandler.class); + + verify(connection).write(eq(runMessage), runHandlerCaptor.capture()); + verify(connection).writeAndFlush(any(PullMessage.class), pullHandlerCaptor.capture()); + + assertThat(runHandlerCaptor.getValue(), instanceOf(RunResponseHandler.class)); + assertThat(pullHandlerCaptor.getValue(), instanceOf(PullAllResponseHandler.class)); + + return runHandlerCaptor.getValue(); + } + + private void verifyBeginInvoked( + Connection connection, + Set bookmarks, + TransactionConfig config, + AccessMode mode, + DatabaseName databaseName) { + ArgumentCaptor beginHandlerCaptor = ArgumentCaptor.forClass(ResponseHandler.class); + BeginMessage beginMessage = new BeginMessage(bookmarks, config, databaseName, mode, null, null, null); + verify(connection).writeAndFlush(eq(beginMessage), beginHandlerCaptor.capture()); + assertThat(beginHandlerCaptor.getValue(), instanceOf(BeginTxResponseHandler.class)); + } + + private static InternalAuthToken dummyAuthToken() { + return (InternalAuthToken) AuthTokens.basic("hello", "world"); + } +} diff --git a/driver/src/test/java/org/neo4j/driver/internal/messaging/v51/MessageFormatV51Test.java b/driver/src/test/java/org/neo4j/driver/internal/messaging/v51/MessageFormatV51Test.java new file mode 100644 index 0000000000..fc2078a11a --- /dev/null +++ b/driver/src/test/java/org/neo4j/driver/internal/messaging/v51/MessageFormatV51Test.java @@ -0,0 +1,47 @@ +/* + * Copyright (c) "Neo4j" + * Neo4j Sweden AB [http://neo4j.com] + * + * This file is part of Neo4j. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.neo4j.driver.internal.messaging.v51; + +import static org.hamcrest.MatcherAssert.assertThat; +import static org.hamcrest.Matchers.instanceOf; +import static org.mockito.Mockito.mock; + +import org.junit.jupiter.api.Test; +import org.neo4j.driver.internal.messaging.MessageFormat; +import org.neo4j.driver.internal.messaging.v5.MessageReaderV5; +import org.neo4j.driver.internal.packstream.PackInput; +import org.neo4j.driver.internal.packstream.PackOutput; + +public class MessageFormatV51Test { + private static final MessageFormat format = BoltProtocolV51.INSTANCE.createMessageFormat(); + + @Test + void shouldCreateCorrectWriter() { + MessageFormat.Writer writer = format.newWriter(mock(PackOutput.class)); + + assertThat(writer, instanceOf(MessageWriterV51.class)); + } + + @Test + void shouldCreateCorrectReader() { + MessageFormat.Reader reader = format.newReader(mock(PackInput.class)); + + assertThat(reader, instanceOf(MessageReaderV5.class)); + } +} diff --git a/driver/src/test/java/org/neo4j/driver/internal/messaging/v51/MessageWriterV51Test.java b/driver/src/test/java/org/neo4j/driver/internal/messaging/v51/MessageWriterV51Test.java new file mode 100644 index 0000000000..4adccaf2d8 --- /dev/null +++ b/driver/src/test/java/org/neo4j/driver/internal/messaging/v51/MessageWriterV51Test.java @@ -0,0 +1,191 @@ +/* + * Copyright (c) "Neo4j" + * Neo4j Sweden AB [http://neo4j.com] + * + * This file is part of Neo4j. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.neo4j.driver.internal.messaging.v51; + +import static java.time.Duration.ofSeconds; +import static java.util.Calendar.DECEMBER; +import static java.util.Collections.emptyMap; +import static java.util.Collections.singletonMap; +import static org.neo4j.driver.AccessMode.READ; +import static org.neo4j.driver.AccessMode.WRITE; +import static org.neo4j.driver.AuthTokens.basic; +import static org.neo4j.driver.Values.point; +import static org.neo4j.driver.Values.value; +import static org.neo4j.driver.internal.DatabaseNameUtil.database; +import static org.neo4j.driver.internal.DatabaseNameUtil.defaultDatabase; +import static org.neo4j.driver.internal.messaging.request.CommitMessage.COMMIT; +import static org.neo4j.driver.internal.messaging.request.DiscardAllMessage.DISCARD_ALL; +import static org.neo4j.driver.internal.messaging.request.GoodbyeMessage.GOODBYE; +import static org.neo4j.driver.internal.messaging.request.PullAllMessage.PULL_ALL; +import static org.neo4j.driver.internal.messaging.request.ResetMessage.RESET; +import static org.neo4j.driver.internal.messaging.request.RollbackMessage.ROLLBACK; +import static org.neo4j.driver.internal.messaging.request.RunWithMetadataMessage.autoCommitTxRunMessage; +import static org.neo4j.driver.internal.messaging.request.RunWithMetadataMessage.unmanagedTxRunMessage; + +import java.time.LocalDate; +import java.time.LocalDateTime; +import java.time.LocalTime; +import java.time.OffsetTime; +import java.time.ZoneId; +import java.time.ZoneOffset; +import java.time.ZonedDateTime; +import java.util.Collections; +import java.util.HashMap; +import java.util.Map; +import java.util.stream.Stream; +import org.neo4j.driver.Query; +import org.neo4j.driver.Value; +import org.neo4j.driver.Values; +import org.neo4j.driver.internal.InternalBookmark; +import org.neo4j.driver.internal.messaging.Message; +import org.neo4j.driver.internal.messaging.MessageFormat; +import org.neo4j.driver.internal.messaging.request.BeginMessage; +import org.neo4j.driver.internal.messaging.request.DiscardMessage; +import org.neo4j.driver.internal.messaging.request.HelloMessage; +import org.neo4j.driver.internal.messaging.request.PullMessage; +import org.neo4j.driver.internal.messaging.request.RouteMessage; +import org.neo4j.driver.internal.packstream.PackOutput; +import org.neo4j.driver.internal.security.InternalAuthToken; +import org.neo4j.driver.internal.util.messaging.AbstractMessageWriterTestBase; + +public class MessageWriterV51Test extends AbstractMessageWriterTestBase { + @Override + protected MessageFormat.Writer newWriter(PackOutput output) { + return BoltProtocolV51.INSTANCE.createMessageFormat().newWriter(output); + } + + @Override + protected Stream supportedMessages() { + return Stream.of( + // Bolt V2 Data Types + unmanagedTxRunMessage(new Query("RETURN $point", singletonMap("point", point(42, 12.99, -180.0)))), + unmanagedTxRunMessage( + new Query("RETURN $point", singletonMap("point", point(42, 0.51, 2.99, 100.123)))), + unmanagedTxRunMessage( + new Query("RETURN $date", singletonMap("date", value(LocalDate.ofEpochDay(2147483650L))))), + unmanagedTxRunMessage(new Query( + "RETURN $time", singletonMap("time", value(OffsetTime.of(4, 16, 20, 999, ZoneOffset.MIN))))), + unmanagedTxRunMessage( + new Query("RETURN $time", singletonMap("time", value(LocalTime.of(12, 9, 18, 999_888))))), + unmanagedTxRunMessage(new Query( + "RETURN $dateTime", + singletonMap("dateTime", value(LocalDateTime.of(2049, DECEMBER, 12, 17, 25, 49, 199))))), + unmanagedTxRunMessage(new Query( + "RETURN $dateTime", + singletonMap( + "dateTime", + value(ZonedDateTime.of( + 2000, 1, 10, 12, 2, 49, 300, ZoneOffset.ofHoursMinutes(9, 30)))))), + unmanagedTxRunMessage(new Query( + "RETURN $dateTime", + singletonMap( + "dateTime", + value(ZonedDateTime.of(2000, 1, 10, 12, 2, 49, 300, ZoneId.of("Europe/Stockholm")))))), + + // New Bolt V4 messages + new PullMessage(100, 200), + new DiscardMessage(300, 400), + + // Bolt V3 messages + new HelloMessage( + "MyDriver/1.2.3", + ((InternalAuthToken) basic("neo4j", "neo4j")).toMap(), + Collections.emptyMap(), + false, + null), + GOODBYE, + new BeginMessage( + Collections.singleton(InternalBookmark.parse("neo4j:bookmark:v1:tx123")), + ofSeconds(5), + singletonMap("key", value(42)), + READ, + defaultDatabase(), + null, + null, + null), + new BeginMessage( + Collections.singleton(InternalBookmark.parse("neo4j:bookmark:v1:tx123")), + ofSeconds(5), + singletonMap("key", value(42)), + WRITE, + database("foo"), + null, + null, + null), + COMMIT, + ROLLBACK, + RESET, + autoCommitTxRunMessage( + new Query("RETURN 1"), + ofSeconds(5), + singletonMap("key", value(42)), + defaultDatabase(), + READ, + Collections.singleton(InternalBookmark.parse("neo4j:bookmark:v1:tx1")), + null, + null), + autoCommitTxRunMessage( + new Query("RETURN 1"), + ofSeconds(5), + singletonMap("key", value(42)), + database("foo"), + WRITE, + Collections.singleton(InternalBookmark.parse("neo4j:bookmark:v1:tx1")), + null, + null), + unmanagedTxRunMessage(new Query("RETURN 1")), + + // Bolt V3 messages with struct values + autoCommitTxRunMessage( + new Query("RETURN $x", singletonMap("x", value(ZonedDateTime.now()))), + ofSeconds(1), + emptyMap(), + defaultDatabase(), + READ, + Collections.emptySet(), + null, + null), + autoCommitTxRunMessage( + new Query("RETURN $x", singletonMap("x", value(ZonedDateTime.now()))), + ofSeconds(1), + emptyMap(), + database("foo"), + WRITE, + Collections.emptySet(), + null, + null), + unmanagedTxRunMessage(new Query("RETURN $x", singletonMap("x", point(42, 1, 2, 3)))), + + // New 4.3 Messages + routeMessage()); + } + + @Override + protected Stream unsupportedMessages() { + return Stream.of( + // Bolt V1, V2 and V3 messages + PULL_ALL, DISCARD_ALL); + } + + private RouteMessage routeMessage() { + Map routeContext = new HashMap<>(); + routeContext.put("someContext", Values.value(124)); + return new RouteMessage(routeContext, Collections.emptySet(), "dbName", null); + } +} diff --git a/driver/src/test/java/org/neo4j/driver/internal/summary/InternalNotificationTest.java b/driver/src/test/java/org/neo4j/driver/internal/summary/InternalNotificationTest.java index 06ef2e9ee7..0e6be509f8 100644 --- a/driver/src/test/java/org/neo4j/driver/internal/summary/InternalNotificationTest.java +++ b/driver/src/test/java/org/neo4j/driver/internal/summary/InternalNotificationTest.java @@ -24,7 +24,10 @@ import java.util.HashMap; import java.util.Map; +import org.hamcrest.junit.MatcherAssert; import org.junit.jupiter.api.Test; +import org.neo4j.driver.NotificationCategory; +import org.neo4j.driver.NotificationSeverity; import org.neo4j.driver.Value; import org.neo4j.driver.internal.value.IntegerValue; import org.neo4j.driver.internal.value.MapValue; @@ -34,6 +37,7 @@ class InternalNotificationTest { @Test + @SuppressWarnings("deprecation") void shouldHandleNotificationWithPosition() { // GIVEN Map map = new HashMap<>(); @@ -41,6 +45,7 @@ void shouldHandleNotificationWithPosition() { map.put("code", new StringValue("Neo.DummyNotification")); map.put("title", new StringValue("A title")); map.put("severity", new StringValue("WARNING")); + map.put("category", new StringValue("DEPRECATION")); Map position = new HashMap<>(); position.put("offset", new IntegerValue(0)); position.put("column", new IntegerValue(1)); @@ -56,6 +61,10 @@ void shouldHandleNotificationWithPosition() { assertThat(notification.code(), equalTo("Neo.DummyNotification")); assertThat(notification.title(), equalTo("A title")); assertThat(notification.severity(), equalTo("WARNING")); + MatcherAssert.assertThat(notification.severityLevel().get(), equalTo(NotificationSeverity.WARNING)); + MatcherAssert.assertThat(notification.rawSeverityLevel().get(), equalTo("WARNING")); + MatcherAssert.assertThat(notification.category().get(), equalTo(NotificationCategory.DEPRECATION)); + MatcherAssert.assertThat(notification.rawCategory().get(), equalTo("DEPRECATION")); InputPosition pos = notification.position(); assertThat(pos.offset(), equalTo(0)); assertThat(pos.column(), equalTo(1)); diff --git a/driver/src/test/java/org/neo4j/driver/internal/util/MessageRecordingDriverFactory.java b/driver/src/test/java/org/neo4j/driver/internal/util/MessageRecordingDriverFactory.java index 12b31319a4..251787afc8 100644 --- a/driver/src/test/java/org/neo4j/driver/internal/util/MessageRecordingDriverFactory.java +++ b/driver/src/test/java/org/neo4j/driver/internal/util/MessageRecordingDriverFactory.java @@ -64,7 +64,8 @@ protected ChannelConnector createConnector( config.logging(), clock, routingContext, - DefaultDomainNameResolver.getInstance()); + DefaultDomainNameResolver.getInstance(), + null); } private class MessageRecordingChannelPipelineBuilder extends ChannelPipelineBuilderImpl { diff --git a/driver/src/test/java/org/neo4j/driver/internal/util/MetadataExtractorTest.java b/driver/src/test/java/org/neo4j/driver/internal/util/MetadataExtractorTest.java index 8aace55e49..819f728b27 100644 --- a/driver/src/test/java/org/neo4j/driver/internal/util/MetadataExtractorTest.java +++ b/driver/src/test/java/org/neo4j/driver/internal/util/MetadataExtractorTest.java @@ -48,6 +48,8 @@ import java.util.concurrent.TimeUnit; import org.junit.jupiter.api.Test; import org.neo4j.driver.Bookmark; +import org.neo4j.driver.NotificationCategory; +import org.neo4j.driver.NotificationSeverity; import org.neo4j.driver.Query; import org.neo4j.driver.Value; import org.neo4j.driver.Values; @@ -261,12 +263,14 @@ void shouldBuildResultSummaryWithoutProfiledPlan() { } @Test + @SuppressWarnings("deprecation") void shouldBuildResultSummaryWithNotifications() { Value notification1 = parameters( "description", "Almost bad thing", "code", "Neo.DummyNotification", "title", "A title", "severity", "WARNING", + "category", "DEPRECATION", "position", parameters( "offset", 42, @@ -295,6 +299,12 @@ void shouldBuildResultSummaryWithNotifications() { assertEquals("Neo.DummyNotification", firstNotification.code()); assertEquals("A title", firstNotification.title()); assertEquals("WARNING", firstNotification.severity()); + assertEquals( + NotificationSeverity.WARNING, firstNotification.severityLevel().get()); + assertEquals("WARNING", firstNotification.rawSeverityLevel().get()); + assertEquals( + NotificationCategory.DEPRECATION, firstNotification.category().get()); + assertEquals("DEPRECATION", firstNotification.rawCategory().get()); assertEquals(new InternalInputPosition(42, 4242, 424242), firstNotification.position()); assertEquals("Almost good thing", secondNotification.description()); diff --git a/driver/src/test/java/org/neo4j/driver/internal/util/io/ChannelTrackingDriverFactoryWithFailingMessageFormat.java b/driver/src/test/java/org/neo4j/driver/internal/util/io/ChannelTrackingDriverFactoryWithFailingMessageFormat.java index 72b89d1c1f..0b686d893e 100644 --- a/driver/src/test/java/org/neo4j/driver/internal/util/io/ChannelTrackingDriverFactoryWithFailingMessageFormat.java +++ b/driver/src/test/java/org/neo4j/driver/internal/util/io/ChannelTrackingDriverFactoryWithFailingMessageFormat.java @@ -50,7 +50,8 @@ protected ChannelConnector createRealConnector( config.logging(), clock, routingContext, - DefaultDomainNameResolver.getInstance()); + DefaultDomainNameResolver.getInstance(), + null); } public FailingMessageFormat getFailingMessageFormat() { 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 a9320edb05..f5f3b2d255 100644 --- a/driver/src/test/java/org/neo4j/driver/testutil/TestUtil.java +++ b/driver/src/test/java/org/neo4j/driver/testutil/TestUtil.java @@ -94,6 +94,7 @@ import org.neo4j.driver.internal.messaging.v43.BoltProtocolV43; import org.neo4j.driver.internal.messaging.v44.BoltProtocolV44; import org.neo4j.driver.internal.messaging.v5.BoltProtocolV5; +import org.neo4j.driver.internal.messaging.v51.BoltProtocolV51; import org.neo4j.driver.internal.retry.RetryLogic; import org.neo4j.driver.internal.spi.Connection; import org.neo4j.driver.internal.spi.ConnectionProvider; @@ -267,7 +268,8 @@ public static NetworkSession newSession( null, UNLIMITED_FETCH_SIZE, DEV_NULL_LOGGING, - NoOpBookmarkManager.INSTANCE); + NoOpBookmarkManager.INSTANCE, + null); } public static void verifyRunRx(Connection connection, String query) { @@ -453,7 +455,8 @@ public static Connection connectionMock(String databaseName, AccessMode mode, Bo || version.equals(BoltProtocolV42.VERSION) || version.equals(BoltProtocolV43.VERSION) || version.equals(BoltProtocolV44.VERSION) - || version.equals(BoltProtocolV5.VERSION)) { + || version.equals(BoltProtocolV5.VERSION) + || version.equals(BoltProtocolV51.VERSION)) { setupSuccessResponse(connection, CommitMessage.class); setupSuccessResponse(connection, RollbackMessage.class); setupSuccessResponse(connection, BeginMessage.class); 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 89919f6214..9f91369751 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 @@ -39,6 +39,8 @@ public class GetFeatures implements TestkitRequest { "Feature:Bolt:4.3", "Feature:Bolt:4.4", "Feature:Bolt:5.0", + "Feature:Bolt:5.1", + "Feature:Bolt:5.2", "AuthorizationExpiredTreatment", "ConfHint:connection.recv_timeout_seconds", "Feature:Auth:Bearer", @@ -58,7 +60,9 @@ public class GetFeatures implements TestkitRequest { "Optimization:ImplicitDefaultArguments", "Feature:Bolt:Patch:UTC", "Feature:API:Type.Temporal", - "Feature:API:BookmarkManager")); + "Feature:API:BookmarkManager", + "Feature:API:Driver:NotificationsConfig", + "Feature:API:Session:NotificationsConfig")); 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 09f02a68fd..f2435d3e9f 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 @@ -27,6 +27,7 @@ import java.util.LinkedHashSet; import java.util.List; import java.util.Optional; +import java.util.Set; import java.util.concurrent.CompletableFuture; import java.util.concurrent.CompletionStage; import java.util.concurrent.TimeUnit; @@ -45,10 +46,13 @@ import org.neo4j.driver.AuthToken; import org.neo4j.driver.AuthTokens; import org.neo4j.driver.Config; +import org.neo4j.driver.NotificationConfig; import org.neo4j.driver.internal.BoltServerAddress; import org.neo4j.driver.internal.DefaultDomainNameResolver; import org.neo4j.driver.internal.DomainNameResolver; import org.neo4j.driver.internal.DriverFactory; +import org.neo4j.driver.internal.InternalNotificationCategory; +import org.neo4j.driver.internal.InternalNotificationSeverity; import org.neo4j.driver.internal.SecuritySettings; import org.neo4j.driver.internal.cluster.loadbalancing.LoadBalancer; import org.neo4j.driver.internal.security.SecurityPlan; @@ -111,6 +115,8 @@ public TestkitResponse process(TestkitState testkitState) { Optional.ofNullable(data.maxConnectionPoolSize).ifPresent(configBuilder::withMaxConnectionPoolSize); Optional.ofNullable(data.connectionAcquisitionTimeoutMs) .ifPresent(timeout -> configBuilder.withConnectionAcquisitionTimeout(timeout, TimeUnit.MILLISECONDS)); + configBuilder.withNotificationConfig( + toNotificationConfig(data.notificationsMinSeverity, data.notificationsDisabledCategories)); configBuilder.withDriverMetrics(); org.neo4j.driver.Driver driver; Config config = configBuilder.build(); @@ -271,6 +277,23 @@ private SecuritySettings.SecuritySettingsBuilder configureSecuritySettingsBuilde return securitySettingsBuilder; } + public static NotificationConfig toNotificationConfig( + String minimumSeverityString, Set disabledCategoryStrings) { + var config = NotificationConfig.defaultConfig(); + config = Optional.ofNullable(minimumSeverityString) + .flatMap(InternalNotificationSeverity::valueOf) + .map(config::enableMinimumSeverity) + .orElse(config); + config = Optional.ofNullable(disabledCategoryStrings) + .map(categories -> categories.stream() + .map(InternalNotificationCategory::valueOf) + .map(opt -> opt.orElse(null)) + .collect(Collectors.toSet())) + .map(config::disableCategories) + .orElse(config); + return config; + } + @Setter @Getter public static class NewDriverBody { @@ -281,6 +304,8 @@ public static class NewDriverBody { private boolean domainNameResolverRegistered; private Long connectionTimeoutMs; private Integer fetchSize; + private String notificationsMinSeverity; + private Set notificationsDisabledCategories; private Long maxTxRetryTimeMs; private Long livenessCheckTimeoutMs; private Integer maxConnectionPoolSize; diff --git a/testkit-backend/src/main/java/neo4j/org/testkit/backend/messages/requests/NewSession.java b/testkit-backend/src/main/java/neo4j/org/testkit/backend/messages/requests/NewSession.java index e17b1d9d5c..968ec77cd9 100644 --- a/testkit-backend/src/main/java/neo4j/org/testkit/backend/messages/requests/NewSession.java +++ b/testkit-backend/src/main/java/neo4j/org/testkit/backend/messages/requests/NewSession.java @@ -18,8 +18,11 @@ */ package neo4j.org.testkit.backend.messages.requests; +import static neo4j.org.testkit.backend.messages.requests.NewDriver.toNotificationConfig; + import java.util.List; import java.util.Optional; +import java.util.Set; import java.util.concurrent.CompletableFuture; import java.util.concurrent.CompletionStage; import java.util.function.BiFunction; @@ -102,6 +105,9 @@ protected TestkitResponse createSessionStateAndResponse( .map(testkitState::getBookmarkManager) .ifPresent(builder::withBookmarkManager); + builder.withNotificationConfig( + toNotificationConfig(data.notificationsMinSeverity, data.notificationsDisabledCategories)); + T sessionStateHolder = sessionStateProducer.apply(driverHolder, builder.build()); String newId = addSessionHolder.apply(sessionStateHolder); @@ -148,5 +154,7 @@ public static class NewSessionBody { private String impersonatedUser; private int fetchSize; private String bookmarkManagerId; + private String notificationsMinSeverity; + private Set notificationsDisabledCategories; } } 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 407fc3943b..dfbea9f77f 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 @@ -72,7 +72,6 @@ public class StartTest implements TestkitRequest { "^.*\\.TestConnectionAcquisitionTimeoutMs\\.test_should_fail_when_acquisition_timeout_is_reached_first.*$", skipMessage); skipMessage = "This test needs updating to implement expected behaviour"; - COMMON_SKIP_PATTERN_TO_REASON.put("^.*\\.TestAuthenticationSchemes\\.test_custom_scheme_empty$", skipMessage); COMMON_SKIP_PATTERN_TO_REASON.put( "^.*\\.TestAuthenticationSchemes[^.]+\\.test_custom_scheme_empty$", skipMessage); skipMessage = "Driver does not implement optimization for qid in explicit transaction"; @@ -82,6 +81,12 @@ public class StartTest implements TestkitRequest { "^.*\\.TestOptimizations\\.test_uses_implicit_default_arguments_multi_query$", skipMessage); COMMON_SKIP_PATTERN_TO_REASON.put( "^.*\\.TestOptimizations\\.test_uses_implicit_default_arguments_multi_query_nested$", skipMessage); + skipMessage = + "Tests for driver with types.Feature.OPT_IMPLICIT_DEFAULT_ARGUMENTS but without types.Feature.OPT_AUTH_PIPELINING are (currently) missing when logon is supported"; + COMMON_SKIP_PATTERN_TO_REASON.put("^.*\\.TestAuthenticationSchemes[^.]+\\.test_basic_scheme$", skipMessage); + COMMON_SKIP_PATTERN_TO_REASON.put("^.*\\.TestAuthenticationSchemes[^.]+\\.test_bearer_scheme$", skipMessage); + COMMON_SKIP_PATTERN_TO_REASON.put("^.*\\.TestAuthenticationSchemes[^.]+\\.test_custom_scheme$", skipMessage); + COMMON_SKIP_PATTERN_TO_REASON.put("^.*\\.TestAuthenticationSchemes[^.]+\\.test_kerberos_scheme$", skipMessage); ASYNC_SKIP_PATTERN_TO_REASON.putAll(COMMON_SKIP_PATTERN_TO_REASON); diff --git a/testkit-backend/src/main/java/neo4j/org/testkit/backend/messages/requests/SummaryUtil.java b/testkit-backend/src/main/java/neo4j/org/testkit/backend/messages/requests/SummaryUtil.java index 16a333d3ec..dd3b39a59f 100644 --- a/testkit-backend/src/main/java/neo4j/org/testkit/backend/messages/requests/SummaryUtil.java +++ b/testkit-backend/src/main/java/neo4j/org/testkit/backend/messages/requests/SummaryUtil.java @@ -24,12 +24,15 @@ import java.util.function.Function; import java.util.stream.Collectors; import neo4j.org.testkit.backend.messages.responses.Summary; +import org.neo4j.driver.internal.InternalNotificationCategory; +import org.neo4j.driver.internal.InternalNotificationSeverity; import org.neo4j.driver.summary.InputPosition; import org.neo4j.driver.summary.Plan; import org.neo4j.driver.summary.ProfiledPlan; import org.neo4j.driver.summary.QueryType; public class SummaryUtil { + @SuppressWarnings("deprecation") public static Summary.SummaryBody toSummaryBody(org.neo4j.driver.summary.ResultSummary summary) { var serverInfo = Summary.ServerInfo.builder() .address(summary.server().address()) @@ -65,6 +68,18 @@ public static Summary.SummaryBody toSummaryBody(org.neo4j.driver.summary.ResultS .description(s.description()) .position(toInputPosition(s.position())) .severity(s.severity()) + .severityLevel(s.severityLevel() + .map(InternalNotificationSeverity.class::cast) + .map(InternalNotificationSeverity::type) + .map(InternalNotificationSeverity.Type::toString) + .orElse("UNKNOWN")) + .rawSeverityLevel(s.rawSeverityLevel().orElse(null)) + .category(s.category() + .map(InternalNotificationCategory.class::cast) + .map(InternalNotificationCategory::type) + .map(InternalNotificationCategory.Type::toString) + .orElse("UNKNOWN")) + .rawCategory(s.rawCategory().orElse(null)) .build()) .collect(Collectors.toList()); return Summary.SummaryBody.builder() diff --git a/testkit-backend/src/main/java/neo4j/org/testkit/backend/messages/responses/Summary.java b/testkit-backend/src/main/java/neo4j/org/testkit/backend/messages/responses/Summary.java index 4a888e4e82..f13b3027e0 100644 --- a/testkit-backend/src/main/java/neo4j/org/testkit/backend/messages/responses/Summary.java +++ b/testkit-backend/src/main/java/neo4j/org/testkit/backend/messages/responses/Summary.java @@ -121,6 +121,14 @@ public static class Notification { private InputPosition position; private String severity; + + private String severityLevel; + + private String rawSeverityLevel; + + private String category; + + private String rawCategory; } @Getter