diff --git a/driver/clirr-ignored-differences.xml b/driver/clirr-ignored-differences.xml index 689965a781..d0beec3a05 100644 --- a/driver/clirr-ignored-differences.xml +++ b/driver/clirr-ignored-differences.xml @@ -603,4 +603,28 @@ org.neo4j.driver.ExecutableQuery withAuthToken(org.neo4j.driver.AuthToken) + + org/neo4j/driver/summary/ResultSummary + 7012 + java.util.Set gqlStatusObjects() + + + + org/neo4j/driver/summary/Notification + 7012 + java.util.Optional inputPosition() + + + + org/neo4j/driver/summary/Notification + 7012 + java.util.Optional classification() + + + + org/neo4j/driver/summary/Notification + 7012 + java.util.Optional rawClassification() + + diff --git a/driver/src/main/java/org/neo4j/driver/Config.java b/driver/src/main/java/org/neo4j/driver/Config.java index 35ac38162c..e5542400ca 100644 --- a/driver/src/main/java/org/neo4j/driver/Config.java +++ b/driver/src/main/java/org/neo4j/driver/Config.java @@ -20,6 +20,7 @@ import static org.neo4j.driver.internal.logging.DevNullLogging.DEV_NULL_LOGGING; import static org.neo4j.driver.internal.util.DriverInfoUtil.driverVersion; +import io.netty.channel.EventLoop; import java.io.File; import java.io.Serial; import java.io.Serializable; @@ -28,9 +29,14 @@ import java.util.Collections; import java.util.List; import java.util.Objects; +import java.util.Optional; +import java.util.Set; import java.util.concurrent.TimeUnit; import java.util.logging.Level; +import java.util.stream.Collectors; +import org.neo4j.driver.async.AsyncSession; import org.neo4j.driver.exceptions.UnsupportedFeatureException; +import org.neo4j.driver.internal.InternalNotificationConfig; import org.neo4j.driver.internal.SecuritySettings; import org.neo4j.driver.internal.async.pool.PoolSettings; import org.neo4j.driver.internal.cluster.RoutingSettings; @@ -39,6 +45,8 @@ import org.neo4j.driver.net.ServerAddressResolver; import org.neo4j.driver.util.Experimental; import org.neo4j.driver.util.Immutable; +import org.neo4j.driver.util.Preview; +import org.neo4j.driver.util.Resource; /** * A configuration class to config driver properties. @@ -223,6 +231,7 @@ public int connectionTimeoutMillis() { /** * Returns the maximum connection pool size. + * * @return the maximum size */ public int maxConnectionPoolSize() { @@ -231,6 +240,7 @@ public int maxConnectionPoolSize() { /** * Returns the connection acquisition timeout in milliseconds. + * * @return the acquisition timeout */ public long connectionAcquisitionTimeoutMillis() { @@ -296,6 +306,7 @@ public long maxTransactionRetryTimeMillis() { /** * Returns the fetch size. + * * @return the fetch size */ public long fetchSize() { @@ -304,6 +315,7 @@ public long fetchSize() { /** * Returns notification config. + * * @return the notification config * @since 5.7 */ @@ -312,7 +324,35 @@ public NotificationConfig notificationConfig() { } /** - * Returns the number of {@link io.netty.channel.EventLoop} threads. + * Returns a minimum notification severity. + * + * @return an {@link Optional} of minimum {@link NotificationSeverity} or an empty {@link Optional} if it is not set + * @since 5.22.0 + */ + @Preview(name = "GQL-status object") + public Optional minimumNotificationSeverity() { + return Optional.ofNullable(((InternalNotificationConfig) notificationConfig).minimumSeverity()); + } + + /** + * Returns a set of disabled notification classifications. + * @return the {@link Set} of disabled {@link NotificationClassification} + * @since 5.22.0 + */ + @Preview(name = "GQL-status object") + public Set disabledNotificationClassifications() { + var disabledCategories = ((InternalNotificationConfig) notificationConfig).disabledCategories(); + return disabledCategories != null + ? ((InternalNotificationConfig) notificationConfig) + .disabledCategories().stream() + .map(NotificationClassification.class::cast) + .collect(Collectors.toUnmodifiableSet()) + : Collections.emptySet(); + } + + /** + * Returns the number of {@link EventLoop} threads. + * * @return the number of threads */ public int eventLoopThreads() { @@ -328,6 +368,7 @@ public boolean isMetricsEnabled() { /** * Returns the {@link MetricsAdapter}. + * * @return the metrics adapter */ public MetricsAdapter metricsAdapter() { @@ -373,6 +414,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 boolean telemetryDisabled = false; @@ -399,7 +441,7 @@ public ConfigBuilder withLogging(Logging logging) { * Enable logging of leaked sessions. *

* Each {@link Session session} is associated with a network connection and thus is a - * {@link org.neo4j.driver.util.Resource resource} that needs to be explicitly closed. + * {@link Resource resource} that needs to be explicitly closed. * Unclosed sessions will result in socket leaks and could cause {@link OutOfMemoryError}s. *

* Session is considered to be leaked when it is finalized via {@link Object#finalize()} while not being @@ -579,8 +621,8 @@ public ConfigBuilder withTrustStrategy(TrustStrategy trustStrategy) { public ConfigBuilder withRoutingTablePurgeDelay(long delay, TimeUnit unit) { var routingTablePurgeDelayMillis = unit.toMillis(delay); if (routingTablePurgeDelayMillis < 0) { - throw new IllegalArgumentException(String.format( - "The routing table purge delay may not be smaller than 0, but was %d %s.", delay, unit)); + throw new IllegalArgumentException( + format("The routing table purge delay may not be smaller than 0, but was %d %s.", delay, unit)); } this.routingTablePurgeDelayMillis = routingTablePurgeDelayMillis; return this; @@ -591,11 +633,11 @@ public ConfigBuilder withRoutingTablePurgeDelay(long delay, TimeUnit unit) { * This config is only valid when the driver is used with servers that support Bolt V4 (Server version 4.0 and later). *

* Bolt V4 enables pulling records in batches to allow client to take control of data population and apply back pressure to server. - * This config specifies the default fetch size for all query runs using {@link Session} and {@link org.neo4j.driver.async.AsyncSession}. + * This config specifies the default fetch size for all query runs using {@link Session} and {@link AsyncSession}. * By default, the value is set to {@code 1000}. * Use {@code -1} to disables back pressure and config client to pull all records at once after each run. *

- * This config only applies to run results obtained via {@link Session} and {@link org.neo4j.driver.async.AsyncSession}. + * This config only applies to run results obtained via {@link Session} and {@link AsyncSession}. * As with the reactive sessions the batch size is managed by the subscription requests instead. * * @param size the default record fetch size when pulling records in batches using Bolt V4. @@ -627,11 +669,11 @@ public ConfigBuilder withConnectionTimeout(long value, TimeUnit unit) { var connectionTimeoutMillis = unit.toMillis(value); if (connectionTimeoutMillis < 0) { throw new IllegalArgumentException( - String.format("The connection timeout may not be smaller than 0, but was %d %s.", value, unit)); + format("The connection timeout may not be smaller than 0, but was %d %s.", value, unit)); } var connectionTimeoutMillisInt = (int) connectionTimeoutMillis; if (connectionTimeoutMillisInt != connectionTimeoutMillis) { - throw new IllegalArgumentException(String.format( + throw new IllegalArgumentException(format( "The connection timeout must represent int value when converted to milliseconds %d.", connectionTimeoutMillis)); } @@ -655,7 +697,7 @@ public ConfigBuilder withMaxTransactionRetryTime(long value, TimeUnit unit) { var maxRetryTimeMs = unit.toMillis(value); if (maxRetryTimeMs < 0) { throw new IllegalArgumentException( - String.format("The max retry time may not be smaller than 0, but was %d %s.", value, unit)); + format("The max retry time may not be smaller than 0, but was %d %s.", value, unit)); } this.maxTransactionRetryTimeMillis = maxRetryTimeMs; return this; @@ -732,7 +774,7 @@ public ConfigBuilder withMetricsAdapter(MetricsAdapter metricsAdapter) { public ConfigBuilder withEventLoopThreads(int size) { if (size < 1) { throw new IllegalArgumentException( - String.format("The event loop thread may not be smaller than 1, but was %d.", size)); + format("The event loop thread may not be smaller than 1, but was %d.", size)); } this.eventLoopThreads = size; return this; @@ -768,6 +810,42 @@ public ConfigBuilder withNotificationConfig(NotificationConfig notificationConfi return this; } + /** + * Sets a minimum severity for notifications produced by the server. + * + * @param minimumNotificationSeverity the minimum notification severity + * @return this builder + * @since 5.22.0 + */ + @Preview(name = "GQL-status object") + public ConfigBuilder withMinimumNotificationSeverity(NotificationSeverity minimumNotificationSeverity) { + if (minimumNotificationSeverity == null) { + notificationConfig = NotificationConfig.disableAllConfig(); + } else { + notificationConfig = notificationConfig.enableMinimumSeverity(minimumNotificationSeverity); + } + return this; + } + + /** + * Sets a set of disabled classifications for notifications produced by the server. + * + * @param disabledNotificationClassifications the set of disabled notification classifications + * @return this builder + * @since 5.22.0 + */ + @Preview(name = "GQL-status object") + public ConfigBuilder withDisabledNotificationClassifications( + Set disabledNotificationClassifications) { + var disabledCategories = disabledNotificationClassifications == null + ? Collections.emptySet() + : disabledNotificationClassifications.stream() + .map(NotificationCategory.class::cast) + .collect(Collectors.toSet()); + notificationConfig = notificationConfig.disableCategories(disabledCategories); + return this; + } + /** * Sets if telemetry is disabled on the driver side. *

diff --git a/driver/src/main/java/org/neo4j/driver/NotificationCategory.java b/driver/src/main/java/org/neo4j/driver/NotificationCategory.java index 65bdecbc8d..f648c939a0 100644 --- a/driver/src/main/java/org/neo4j/driver/NotificationCategory.java +++ b/driver/src/main/java/org/neo4j/driver/NotificationCategory.java @@ -17,28 +17,26 @@ 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 { +public sealed interface NotificationCategory extends Serializable permits NotificationClassification { /** * A hint category. *

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

* For instance, the query or command mentions entities that are unknown to the system. */ - NotificationCategory UNRECOGNIZED = new InternalNotificationCategory(Type.UNRECOGNIZED); + NotificationCategory UNRECOGNIZED = NotificationClassification.UNRECOGNIZED; /** * An unsupported category. @@ -46,21 +44,21 @@ public sealed interface NotificationCategory extends Serializable permits Intern * 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); + NotificationCategory UNSUPPORTED = NotificationClassification.UNSUPPORTED; /** * A performance category. *

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

* For instance, the query/command use deprecated features that should be replaced. */ - NotificationCategory DEPRECATION = new InternalNotificationCategory(Type.DEPRECATION); + NotificationCategory DEPRECATION = NotificationClassification.DEPRECATION; /** * A security category. @@ -72,7 +70,7 @@ public sealed interface NotificationCategory extends Serializable permits Intern * * @since 5.14 */ - NotificationCategory SECURITY = new InternalNotificationCategory(Type.SECURITY); + NotificationCategory SECURITY = NotificationClassification.SECURITY; /** * A topology category. @@ -84,12 +82,12 @@ public sealed interface NotificationCategory extends Serializable permits Intern * * @since 5.14 */ - NotificationCategory TOPOLOGY = new InternalNotificationCategory(Type.TOPOLOGY); + NotificationCategory TOPOLOGY = NotificationClassification.TOPOLOGY; /** * A generic category. *

* For instance, notifications that are not part of a more specific class. */ - NotificationCategory GENERIC = new InternalNotificationCategory(Type.GENERIC); + NotificationCategory GENERIC = NotificationClassification.GENERIC; } diff --git a/driver/src/main/java/org/neo4j/driver/NotificationClassification.java b/driver/src/main/java/org/neo4j/driver/NotificationClassification.java new file mode 100644 index 0000000000..ad3c794b73 --- /dev/null +++ b/driver/src/main/java/org/neo4j/driver/NotificationClassification.java @@ -0,0 +1,90 @@ +/* + * Copyright (c) "Neo4j" + * Neo4j Sweden AB [https://neo4j.com] + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.neo4j.driver; + +import org.neo4j.driver.util.Preview; + +/** + * Notification classification. + * + * @since 5.22.0 + */ +@Preview(name = "GQL-status object") +public enum NotificationClassification implements NotificationCategory { + /** + * A hint category. + *

+ * For instance, the given hint cannot be satisfied. + */ + HINT, + + /** + * An unrecognized category. + *

+ * For instance, the query or command mentions entities that are unknown to the system. + */ + 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. + */ + UNSUPPORTED, + + /** + * A performance category. + *

+ * For instance, the query uses costly operations and might be slow. + */ + PERFORMANCE, + + /** + * A deprecation category. + *

+ * For instance, the query/command use deprecated features that should be replaced. + */ + DEPRECATION, + + /** + * A security category. + *

+ * For instance, the security warnings. + *

+ * Please note that this category was added to a later server version. Therefore, a compatible server version is + * required to use it. + */ + SECURITY, + + /** + * A topology category. + *

+ * For instance, the topology notifications. + *

+ * Please note that this category was added to a later server version. Therefore, a compatible server version is + * required to use it. + */ + TOPOLOGY, + + /** + * A generic category. + *

+ * For instance, notifications that are not part of a more specific class. + */ + GENERIC +} diff --git a/driver/src/main/java/org/neo4j/driver/NotificationConfig.java b/driver/src/main/java/org/neo4j/driver/NotificationConfig.java index 5224dfcc0f..ff85533909 100644 --- a/driver/src/main/java/org/neo4j/driver/NotificationConfig.java +++ b/driver/src/main/java/org/neo4j/driver/NotificationConfig.java @@ -21,6 +21,7 @@ import org.neo4j.driver.internal.InternalNotificationConfig; import org.neo4j.driver.internal.InternalNotificationSeverity; import org.neo4j.driver.summary.ResultSummary; +import org.neo4j.driver.util.Preview; /** * A notification configuration defining what notifications should be supplied by the server. @@ -39,6 +40,7 @@ * @see ResultSummary#notifications() * @see org.neo4j.driver.summary.Notification */ +@Preview(name = "GQL-status object") public sealed interface NotificationConfig extends Serializable permits InternalNotificationConfig { /** * Returns a default notification configuration. diff --git a/driver/src/main/java/org/neo4j/driver/NotificationSeverity.java b/driver/src/main/java/org/neo4j/driver/NotificationSeverity.java index 09dbcbcc93..d503142686 100644 --- a/driver/src/main/java/org/neo4j/driver/NotificationSeverity.java +++ b/driver/src/main/java/org/neo4j/driver/NotificationSeverity.java @@ -19,7 +19,9 @@ import static org.neo4j.driver.internal.InternalNotificationSeverity.Type; import java.io.Serializable; +import org.neo4j.driver.Config.ConfigBuilder; import org.neo4j.driver.internal.InternalNotificationSeverity; +import org.neo4j.driver.util.Preview; /** * Notification severity level. @@ -36,4 +38,12 @@ public sealed interface NotificationSeverity extends Serializable, Comparable minimumNotificationSeverity() { + return Optional.ofNullable(((InternalNotificationConfig) notificationConfig).minimumSeverity()); + } + + /** + * Returns a set of disabled notification classifications. + * @return the {@link Set} of disabled {@link NotificationClassification} + * @since 5.22.0 + */ + @Preview(name = "GQL-status object") + public Set disabledNotificationClassifications() { + var disabledCategories = ((InternalNotificationConfig) notificationConfig).disabledCategories(); + return disabledCategories != null + ? ((InternalNotificationConfig) notificationConfig) + .disabledCategories().stream() + .map(NotificationClassification.class::cast) + .collect(Collectors.toUnmodifiableSet()) + : Collections.emptySet(); + } + @Override public boolean equals(Object o) { if (this == o) { @@ -214,6 +246,7 @@ public static final class Builder { private String database = null; private String impersonatedUser = null; private BookmarkManager bookmarkManager; + private NotificationConfig notificationConfig = NotificationConfig.defaultConfig(); private Builder() {} @@ -397,6 +430,42 @@ public Builder withNotificationConfig(NotificationConfig notificationConfig) { return this; } + /** + * Sets a minimum severity for notifications produced by the server. + * + * @param minimumNotificationSeverity the minimum notification severity + * @return this builder + * @since 5.22.0 + */ + @Preview(name = "GQL-status object") + public Builder withMinimumNotificationSeverity(NotificationSeverity minimumNotificationSeverity) { + if (minimumNotificationSeverity == null) { + notificationConfig = NotificationConfig.disableAllConfig(); + } else { + notificationConfig = notificationConfig.enableMinimumSeverity(minimumNotificationSeverity); + } + return this; + } + + /** + * Sets a set of disabled classifications for notifications produced by the server. + * + * @param disabledNotificationClassifications the set of disabled notification classifications + * @return this builder + * @since 5.22.0 + */ + @Preview(name = "GQL-status object") + public Builder withDisabledNotificationClassifications( + Set disabledNotificationClassifications) { + var disabledCategories = disabledNotificationClassifications == null + ? Collections.emptySet() + : disabledNotificationClassifications.stream() + .map(NotificationCategory.class::cast) + .collect(Collectors.toSet()); + notificationConfig = notificationConfig.disableCategories(disabledCategories); + return this; + } + /** * Builds the {@link SessionConfig}. * @return the config 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 273d9f97d5..61ade28328 100644 --- a/driver/src/main/java/org/neo4j/driver/internal/DriverFactory.java +++ b/driver/src/main/java/org/neo4j/driver/internal/DriverFactory.java @@ -183,7 +183,7 @@ protected ChannelConnector createConnector( clock, routingContext, getDomainNameResolver(), - config.notificationConfig(), + GqlNotificationConfig.from(config.notificationConfig()), boltAgent); } diff --git a/driver/src/main/java/org/neo4j/driver/internal/GqlNotificationConfig.java b/driver/src/main/java/org/neo4j/driver/internal/GqlNotificationConfig.java new file mode 100644 index 0000000000..2a7f888c55 --- /dev/null +++ b/driver/src/main/java/org/neo4j/driver/internal/GqlNotificationConfig.java @@ -0,0 +1,42 @@ +/* + * Copyright (c) "Neo4j" + * Neo4j Sweden AB [https://neo4j.com] + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.neo4j.driver.internal; + +import java.util.Objects; +import java.util.Set; +import java.util.stream.Collectors; +import org.neo4j.driver.NotificationClassification; +import org.neo4j.driver.NotificationConfig; +import org.neo4j.driver.NotificationSeverity; + +public record GqlNotificationConfig( + NotificationSeverity minimumSeverity, Set disabledClassifications) { + public static GqlNotificationConfig defaultConfig() { + return new GqlNotificationConfig(null, null); + } + + public static GqlNotificationConfig from(NotificationConfig notificationConfig) { + Objects.requireNonNull(notificationConfig); + var config = (InternalNotificationConfig) notificationConfig; + var disabledClassifications = config.disabledCategories() != null + ? config.disabledCategories().stream() + .map(NotificationClassification.class::cast) + .collect(Collectors.toUnmodifiableSet()) + : null; + return new GqlNotificationConfig(config.minimumSeverity(), disabledClassifications); + } +} diff --git a/driver/src/main/java/org/neo4j/driver/internal/InternalNotificationCategory.java b/driver/src/main/java/org/neo4j/driver/internal/InternalNotificationCategory.java deleted file mode 100644 index 7f6762edc9..0000000000 --- a/driver/src/main/java/org/neo4j/driver/internal/InternalNotificationCategory.java +++ /dev/null @@ -1,56 +0,0 @@ -/* - * Copyright (c) "Neo4j" - * Neo4j Sweden AB [https://neo4j.com] - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ -package org.neo4j.driver.internal; - -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, - SECURITY, - TOPOLOGY, - 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 SECURITY -> NotificationCategory.SECURITY; - case TOPOLOGY -> NotificationCategory.TOPOLOGY; - 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 index a27c4ec602..ef85db7fb1 100644 --- a/driver/src/main/java/org/neo4j/driver/internal/InternalNotificationConfig.java +++ b/driver/src/main/java/org/neo4j/driver/internal/InternalNotificationConfig.java @@ -33,7 +33,7 @@ public NotificationConfig enableMinimumSeverity(NotificationSeverity minimumSeve @Override public NotificationConfig disableCategories(Set disabledCategories) { - Objects.requireNonNull(disabledCategories, "disabledCategories must not be null"); + Objects.requireNonNull(disabledCategories, "disabledClassifications 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 index 1244cf72b2..d0821526ce 100644 --- a/driver/src/main/java/org/neo4j/driver/internal/InternalNotificationSeverity.java +++ b/driver/src/main/java/org/neo4j/driver/internal/InternalNotificationSeverity.java @@ -22,9 +22,6 @@ 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"); } @@ -47,7 +44,7 @@ public static Optional valueOf(String value) { .map(type -> switch (type) { case INFORMATION -> NotificationSeverity.INFORMATION; case WARNING -> NotificationSeverity.WARNING; - case OFF -> InternalNotificationSeverity.OFF; + case OFF -> NotificationSeverity.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 de8ac27b94..4d2ac34073 100644 --- a/driver/src/main/java/org/neo4j/driver/internal/SessionFactoryImpl.java +++ b/driver/src/main/java/org/neo4j/driver/internal/SessionFactoryImpl.java @@ -28,7 +28,6 @@ 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; @@ -63,7 +62,7 @@ public NetworkSession newInstance( sessionConfig.impersonatedUser().orElse(null), logging, sessionConfig.bookmarkManager().orElse(NoOpBookmarkManager.INSTANCE), - sessionConfig.notificationConfig(), + GqlNotificationConfig.from(sessionConfig.notificationConfig()), overrideAuthToken, telemetryDisabled); } @@ -141,7 +140,7 @@ private NetworkSession createSession( String impersonatedUser, Logging logging, BookmarkManager bookmarkManager, - NotificationConfig notificationConfig, + GqlNotificationConfig notificationConfig, AuthToken authToken, boolean telemetryDisabled) { Objects.requireNonNull(bookmarks, "bookmarks may not be null"); diff --git a/driver/src/main/java/org/neo4j/driver/internal/StatusNotificationConfig.java b/driver/src/main/java/org/neo4j/driver/internal/StatusNotificationConfig.java new file mode 100644 index 0000000000..5136fecc55 --- /dev/null +++ b/driver/src/main/java/org/neo4j/driver/internal/StatusNotificationConfig.java @@ -0,0 +1,25 @@ +/* + * Copyright (c) "Neo4j" + * Neo4j Sweden AB [https://neo4j.com] + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.neo4j.driver.internal; + +import java.util.Set; +import org.neo4j.driver.NotificationClassification; +import org.neo4j.driver.NotificationSeverity; + +public record StatusNotificationConfig( + NotificationSeverity minimumNotificationSeverity, + Set disabledNotificationClassifications) {} 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 f05ec9a4b1..62492175de 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 @@ -26,8 +26,8 @@ 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.GqlNotificationConfig; import org.neo4j.driver.internal.retry.RetryLogic; import org.neo4j.driver.internal.spi.ConnectionProvider; import org.neo4j.driver.internal.util.Futures; @@ -45,7 +45,7 @@ public LeakLoggingNetworkSession( long fetchSize, Logging logging, BookmarkManager bookmarkManager, - NotificationConfig notificationConfig, + GqlNotificationConfig notificationConfig, AuthToken overrideAuthToken, boolean telemetryDisabled) { super( 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 cc102342c1..4c036fe297 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 @@ -35,7 +35,6 @@ 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; @@ -44,6 +43,7 @@ import org.neo4j.driver.internal.DatabaseBookmark; import org.neo4j.driver.internal.DatabaseName; import org.neo4j.driver.internal.FailableCursor; +import org.neo4j.driver.internal.GqlNotificationConfig; import org.neo4j.driver.internal.ImpersonationUtil; import org.neo4j.driver.internal.cursor.AsyncResultCursor; import org.neo4j.driver.internal.cursor.ResultCursorFactory; @@ -73,7 +73,7 @@ public class NetworkSession { private final BookmarkManager bookmarkManager; private volatile Set lastUsedBookmarks = Collections.emptySet(); private volatile Set lastReceivedBookmarks; - private final NotificationConfig notificationConfig; + private final GqlNotificationConfig notificationConfig; private final boolean telemetryDisabled; public NetworkSession( @@ -86,7 +86,7 @@ public NetworkSession( long fetchSize, Logging logging, BookmarkManager bookmarkManager, - NotificationConfig notificationConfig, + GqlNotificationConfig notificationConfig, AuthToken overrideAuthToken, boolean telemetryDisabled) { Objects.requireNonNull(bookmarks, "bookmarks may not be null"); 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 5b58e37722..cc04a529e8 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 @@ -36,7 +36,6 @@ import java.util.function.Function; import org.neo4j.driver.Bookmark; 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; @@ -45,6 +44,7 @@ import org.neo4j.driver.exceptions.ConnectionReadTimeoutException; import org.neo4j.driver.exceptions.TransactionTerminatedException; import org.neo4j.driver.internal.DatabaseBookmark; +import org.neo4j.driver.internal.GqlNotificationConfig; import org.neo4j.driver.internal.cursor.AsyncResultCursor; import org.neo4j.driver.internal.cursor.RxResultCursor; import org.neo4j.driver.internal.messaging.BoltProtocol; @@ -97,7 +97,7 @@ private enum State { private CompletableFuture rollbackFuture; private Throwable causeOfTermination; private CompletionStage terminationStage; - private final NotificationConfig notificationConfig; + private final GqlNotificationConfig notificationConfig; private final CompletableFuture beginFuture = new CompletableFuture<>(); private final Logging logging; @@ -107,7 +107,7 @@ public UnmanagedTransaction( Connection connection, Consumer bookmarkConsumer, long fetchSize, - NotificationConfig notificationConfig, + GqlNotificationConfig notificationConfig, ApiTelemetryWork apiTelemetryWork, Logging logging) { this( @@ -125,7 +125,7 @@ protected UnmanagedTransaction( Consumer bookmarkConsumer, long fetchSize, ResultCursorsHolder resultCursors, - NotificationConfig notificationConfig, + GqlNotificationConfig notificationConfig, ApiTelemetryWork apiTelemetryWork, Logging logging) { this.connection = connection; 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 268d913b97..1f9e138cb1 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 @@ -27,7 +27,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.v54.BoltProtocolV54; +import org.neo4j.driver.internal.messaging.v55.BoltProtocolV55; public final class BoltProtocolUtil { public static final int BOLT_MAGIC_PREAMBLE = 0x6060B017; @@ -39,7 +39,7 @@ public final class BoltProtocolUtil { private static final ByteBuf HANDSHAKE_BUF = unreleasableBuffer(copyInt( BOLT_MAGIC_PREAMBLE, - BoltProtocolV54.VERSION.toIntRange(BoltProtocolV5.VERSION), + BoltProtocolV55.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/ChannelAttributes.java b/driver/src/main/java/org/neo4j/driver/internal/async/connection/ChannelAttributes.java index ac2eaaf988..55666f1405 100644 --- a/driver/src/main/java/org/neo4j/driver/internal/async/connection/ChannelAttributes.java +++ b/driver/src/main/java/org/neo4j/driver/internal/async/connection/ChannelAttributes.java @@ -50,8 +50,8 @@ public final class ChannelAttributes { // configuration hints provided by the server private static final AttributeKey CONNECTION_READ_TIMEOUT = newInstance("connectionReadTimeout"); - private static final AttributeKey TELEMETRY_ENABLED = newInstance("telemetryEnabled"); + private static final AttributeKey GQL_STATUS_ENABLED = newInstance("tryUseGqlStatus"); private ChannelAttributes() {} @@ -178,10 +178,18 @@ public static void setTelemetryEnabled(Channel channel, Boolean telemetryEnabled setOnce(channel, TELEMETRY_ENABLED, telemetryEnabled); } - public static Boolean telemetryEnabled(Channel channel) { + public static boolean telemetryEnabled(Channel channel) { return Optional.ofNullable(get(channel, TELEMETRY_ENABLED)).orElse(false); } + public static void setGqlStatusEnabled(Channel channel, Boolean gqlStatusEnabled) { + setOnce(channel, GQL_STATUS_ENABLED, gqlStatusEnabled); + } + + public static boolean gqlStatusEnabled(Channel channel) { + return Optional.ofNullable(get(channel, GQL_STATUS_ENABLED)).orElse(false); + } + private static T get(Channel channel, AttributeKey key) { return channel.attr(key).get(); } 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 59e7a5fe84..6bbe794aec 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 @@ -30,11 +30,11 @@ import java.util.function.Function; import org.neo4j.driver.AuthTokenManager; import org.neo4j.driver.Logging; -import org.neo4j.driver.NotificationConfig; import org.neo4j.driver.internal.BoltAgent; import org.neo4j.driver.internal.BoltServerAddress; import org.neo4j.driver.internal.ConnectionSettings; import org.neo4j.driver.internal.DomainNameResolver; +import org.neo4j.driver.internal.GqlNotificationConfig; import org.neo4j.driver.internal.async.inbound.ConnectTimeoutHandler; import org.neo4j.driver.internal.cluster.RoutingContext; import org.neo4j.driver.internal.security.SecurityPlan; @@ -52,7 +52,7 @@ public class ChannelConnectorImpl implements ChannelConnector { private final Clock clock; private final DomainNameResolver domainNameResolver; private final AddressResolverGroup addressResolverGroup; - private final NotificationConfig notificationConfig; + private final GqlNotificationConfig notificationConfig; public ChannelConnectorImpl( ConnectionSettings connectionSettings, @@ -61,7 +61,7 @@ public ChannelConnectorImpl( Clock clock, RoutingContext routingContext, DomainNameResolver domainNameResolver, - NotificationConfig notificationConfig, + GqlNotificationConfig notificationConfig, BoltAgent boltAgent) { this( connectionSettings, @@ -83,7 +83,7 @@ public ChannelConnectorImpl( Clock clock, RoutingContext routingContext, DomainNameResolver domainNameResolver, - NotificationConfig notificationConfig, + GqlNotificationConfig notificationConfig, BoltAgent boltAgent) { this.userAgent = connectionSettings.userAgent(); this.boltAgent = requireNonNull(boltAgent); 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 b848b54c09..b125d65bb6 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 @@ -23,8 +23,8 @@ import io.netty.channel.ChannelFutureListener; import io.netty.channel.ChannelPromise; import java.time.Clock; -import org.neo4j.driver.NotificationConfig; import org.neo4j.driver.internal.BoltAgent; +import org.neo4j.driver.internal.GqlNotificationConfig; import org.neo4j.driver.internal.cluster.RoutingContext; import org.neo4j.driver.internal.messaging.BoltProtocol; import org.neo4j.driver.internal.messaging.v51.BoltProtocolV51; @@ -34,7 +34,7 @@ public class HandshakeCompletedListener implements ChannelFutureListener { private final BoltAgent boltAgent; private final RoutingContext routingContext; private final ChannelPromise connectionInitializedPromise; - private final NotificationConfig notificationConfig; + private final GqlNotificationConfig notificationConfig; private final Clock clock; public HandshakeCompletedListener( @@ -42,7 +42,7 @@ public HandshakeCompletedListener( BoltAgent boltAgent, RoutingContext routingContext, ChannelPromise connectionInitializedPromise, - NotificationConfig notificationConfig, + GqlNotificationConfig notificationConfig, Clock clock) { requireNonNull(clock, "clock must not be null"); this.userAgent = requireNonNull(userAgent); diff --git a/driver/src/main/java/org/neo4j/driver/internal/handlers/AbstractRecordStateResponseHandler.java b/driver/src/main/java/org/neo4j/driver/internal/handlers/AbstractRecordStateResponseHandler.java new file mode 100644 index 0000000000..24d5792e2c --- /dev/null +++ b/driver/src/main/java/org/neo4j/driver/internal/handlers/AbstractRecordStateResponseHandler.java @@ -0,0 +1,41 @@ +/* + * Copyright (c) "Neo4j" + * Neo4j Sweden AB [https://neo4j.com] + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.neo4j.driver.internal.handlers; + +import java.util.List; +import org.neo4j.driver.internal.summary.InternalGqlStatusObject; +import org.neo4j.driver.summary.GqlStatusObject; + +public abstract class AbstractRecordStateResponseHandler { + protected RecordState recordState = RecordState.NOT_REQUESTED; + + protected synchronized GqlStatusObject generateGqlStatusObject(List keys) { + return switch (recordState) { + case NOT_REQUESTED -> keys.isEmpty() + ? InternalGqlStatusObject.OMITTED_RESULT + : InternalGqlStatusObject.NO_DATA_UNKNOWN; + case HAD_RECORD -> InternalGqlStatusObject.SUCCESS; + case NO_RECORD -> keys.isEmpty() ? InternalGqlStatusObject.OMITTED_RESULT : InternalGqlStatusObject.NO_DATA; + }; + } + + protected enum RecordState { + NOT_REQUESTED, + HAD_RECORD, + NO_RECORD + } +} diff --git a/driver/src/main/java/org/neo4j/driver/internal/handlers/LegacyPullAllResponseHandler.java b/driver/src/main/java/org/neo4j/driver/internal/handlers/LegacyPullAllResponseHandler.java index 726d2a2da3..e8073f6e61 100644 --- a/driver/src/main/java/org/neo4j/driver/internal/handlers/LegacyPullAllResponseHandler.java +++ b/driver/src/main/java/org/neo4j/driver/internal/handlers/LegacyPullAllResponseHandler.java @@ -36,16 +36,18 @@ import org.neo4j.driver.exceptions.Neo4jException; import org.neo4j.driver.internal.InternalRecord; import org.neo4j.driver.internal.messaging.request.PullAllMessage; +import org.neo4j.driver.internal.messaging.v55.BoltProtocolV55; import org.neo4j.driver.internal.spi.Connection; import org.neo4j.driver.internal.util.Futures; import org.neo4j.driver.internal.util.Iterables; import org.neo4j.driver.internal.util.MetadataExtractor; +import org.neo4j.driver.summary.GqlStatusObject; import org.neo4j.driver.summary.ResultSummary; /** * This is the Pull All response handler that handles pull all messages in Bolt v3 and previous protocol versions. */ -public class LegacyPullAllResponseHandler implements PullAllResponseHandler { +public class LegacyPullAllResponseHandler extends AbstractRecordStateResponseHandler implements PullAllResponseHandler { private static final Queue UNINITIALIZED_RECORDS = Iterables.emptyQueue(); static final int RECORD_BUFFER_LOW_WATERMARK = Integer.getInteger("recordBufferLowWatermark", 300); @@ -92,7 +94,9 @@ public synchronized void onSuccess(Map metadata) { finished = true; Neo4jException exception = null; try { - summary = extractResultSummary(metadata); + summary = extractResultSummary( + metadata, + generateGqlStatusObject(runResponseHandler.queryKeys().keys())); } catch (Neo4jException e) { exception = e; } @@ -110,7 +114,7 @@ public synchronized void onSuccess(Map metadata) { @Override public synchronized void onFailure(Throwable error) { finished = true; - summary = extractResultSummary(emptyMap()); + summary = extractResultSummary(emptyMap(), null); completionListener.afterFailure(error); @@ -129,6 +133,7 @@ public synchronized void onFailure(Throwable error) { @Override public synchronized void onRecord(Value[] fields) { + recordState = RecordState.HAD_RECORD; if (ignoreRecords) { completeRecordFuture(null); } else { @@ -189,6 +194,9 @@ public synchronized CompletionStage> listAsync(Function m @Override public void prePopulateRecords() { + synchronized (this) { + recordState = RecordState.NO_RECORD; + } connection.writeAndFlush(PullAllMessage.PULL_ALL, this); } @@ -291,9 +299,15 @@ private boolean completeFailureFuture(Throwable error) { return false; } - private ResultSummary extractResultSummary(Map metadata) { + private ResultSummary extractResultSummary(Map metadata, GqlStatusObject gqlStatusObject) { var resultAvailableAfter = runResponseHandler.resultAvailableAfter(); - return metadataExtractor.extractSummary(query, connection, resultAvailableAfter, metadata); + return metadataExtractor.extractSummary( + query, + connection, + resultAvailableAfter, + metadata, + connection.protocol().version().compareTo(BoltProtocolV55.VERSION) < 0, + gqlStatusObject); } private void enableAutoRead() { diff --git a/driver/src/main/java/org/neo4j/driver/internal/handlers/pulln/BasicPullResponseHandler.java b/driver/src/main/java/org/neo4j/driver/internal/handlers/pulln/BasicPullResponseHandler.java index 017319310e..d2681786bb 100644 --- a/driver/src/main/java/org/neo4j/driver/internal/handlers/pulln/BasicPullResponseHandler.java +++ b/driver/src/main/java/org/neo4j/driver/internal/handlers/pulln/BasicPullResponseHandler.java @@ -29,18 +29,21 @@ import org.neo4j.driver.Value; import org.neo4j.driver.exceptions.Neo4jException; import org.neo4j.driver.internal.InternalRecord; +import org.neo4j.driver.internal.handlers.AbstractRecordStateResponseHandler; import org.neo4j.driver.internal.handlers.PullResponseCompletionListener; import org.neo4j.driver.internal.handlers.RunResponseHandler; import org.neo4j.driver.internal.messaging.request.PullMessage; +import org.neo4j.driver.internal.messaging.v55.BoltProtocolV55; import org.neo4j.driver.internal.spi.Connection; import org.neo4j.driver.internal.util.MetadataExtractor; import org.neo4j.driver.internal.value.BooleanValue; +import org.neo4j.driver.summary.GqlStatusObject; import org.neo4j.driver.summary.ResultSummary; /** * Provides basic handling of pull responses from sever. The state is managed by {@link State}. */ -public class BasicPullResponseHandler implements PullResponseHandler { +public class BasicPullResponseHandler extends AbstractRecordStateResponseHandler implements PullResponseHandler { private static final Runnable NO_OP_RUNNABLE = () -> {}; private final Query query; protected final RunResponseHandler runResponseHandler; @@ -94,9 +97,15 @@ public void onSuccess(Map metadata) { if (newState == State.SUCCEEDED_STATE) { completionListener.afterSuccess(metadata); try { - summary = extractResultSummary(metadata); + summary = extractResultSummary( + metadata, + generateGqlStatusObject( + runResponseHandler.queryKeys().keys())); } catch (Neo4jException e) { - summary = extractResultSummary(emptyMap()); + summary = extractResultSummary( + emptyMap(), + generateGqlStatusObject( + runResponseHandler.queryKeys().keys())); exception = e; } recordConsumer = this.recordConsumer; @@ -128,7 +137,7 @@ public void onFailure(Throwable error) { assertRecordAndSummaryConsumerInstalled(); state.onFailure(this); completionListener.afterFailure(error); - summary = extractResultSummary(emptyMap()); + summary = extractResultSummary(emptyMap(), null); recordConsumer = this.recordConsumer; summaryConsumer = this.summaryConsumer; if (syncSignals) { @@ -147,6 +156,7 @@ public void onRecord(Value[] fields) { Record record = null; synchronized (this) { assertRecordAndSummaryConsumerInstalled(); + recordState = RecordState.HAD_RECORD; state.onRecord(this); newState = state; if (newState == State.STREAMING_STATE) { @@ -166,6 +176,7 @@ public void request(long size) { Runnable postAction; synchronized (this) { assertRecordAndSummaryConsumerInstalled(); + recordState = RecordState.NO_RECORD; postAction = state.request(this, size); if (syncSignals) { postAction.run(); @@ -219,9 +230,15 @@ protected boolean isDone() { return state.equals(State.SUCCEEDED_STATE) || state.equals(State.FAILURE_STATE); } - private ResultSummary extractResultSummary(Map metadata) { + private ResultSummary extractResultSummary(Map metadata, GqlStatusObject gqlStatusObject) { var resultAvailableAfter = runResponseHandler.resultAvailableAfter(); - return metadataExtractor.extractSummary(query, connection, resultAvailableAfter, metadata); + return metadataExtractor.extractSummary( + query, + connection, + resultAvailableAfter, + metadata, + connection.protocol().version().compareTo(BoltProtocolV55.VERSION) < 0, + gqlStatusObject); } private void addToRequest(long toAdd) { 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 bb687acb30..15181cc8cd 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,7 +27,6 @@ import org.neo4j.driver.AuthToken; import org.neo4j.driver.Bookmark; import org.neo4j.driver.Logging; -import org.neo4j.driver.NotificationConfig; import org.neo4j.driver.Query; import org.neo4j.driver.Session; import org.neo4j.driver.Transaction; @@ -35,6 +34,7 @@ import org.neo4j.driver.exceptions.ClientException; import org.neo4j.driver.internal.BoltAgent; import org.neo4j.driver.internal.DatabaseBookmark; +import org.neo4j.driver.internal.GqlNotificationConfig; import org.neo4j.driver.internal.async.UnmanagedTransaction; import org.neo4j.driver.internal.cluster.RoutingContext; import org.neo4j.driver.internal.cursor.ResultCursorFactory; @@ -49,6 +49,7 @@ import org.neo4j.driver.internal.messaging.v52.BoltProtocolV52; import org.neo4j.driver.internal.messaging.v53.BoltProtocolV53; import org.neo4j.driver.internal.messaging.v54.BoltProtocolV54; +import org.neo4j.driver.internal.messaging.v55.BoltProtocolV55; import org.neo4j.driver.internal.spi.Connection; public interface BoltProtocol { @@ -67,7 +68,7 @@ public interface BoltProtocol { * @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 + * @param GqlNotificationConfig the notification configuration * @param clock the clock to use */ void initializeChannel( @@ -76,7 +77,7 @@ void initializeChannel( AuthToken authToken, RoutingContext routingContext, ChannelPromise channelInitializedPromise, - NotificationConfig notificationConfig, + GqlNotificationConfig GqlNotificationConfig, Clock clock); /** @@ -92,7 +93,7 @@ void initializeChannel( * @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 + * @param GqlNotificationConfig the notification configuration * @param logging the driver logging * @param flush defines whether to flush the message to the connection * @return a completion stage completed when transaction is started or completed exceptionally when there was a failure. @@ -102,7 +103,7 @@ CompletionStage beginTransaction( Set bookmarks, TransactionConfig config, String txType, - NotificationConfig notificationConfig, + GqlNotificationConfig GqlNotificationConfig, Logging logging, boolean flush); @@ -138,7 +139,7 @@ CompletionStage beginTransaction( * @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 + * @param GqlNotificationConfig the notification configuration * @param logging the driver logging * @return stage with cursor. */ @@ -149,7 +150,7 @@ ResultCursorFactory runInAutoCommitTransaction( Consumer bookmarkConsumer, TransactionConfig config, long fetchSize, - NotificationConfig notificationConfig, + GqlNotificationConfig GqlNotificationConfig, Logging logging); /** @@ -211,6 +212,8 @@ static BoltProtocol forVersion(BoltProtocolVersion version) { return BoltProtocolV53.INSTANCE; } else if (BoltProtocolV54.VERSION.equals(version)) { return BoltProtocolV54.INSTANCE; + } else if (BoltProtocolV55.VERSION.equals(version)) { + return BoltProtocolV55.INSTANCE; } throw new ClientException("Unknown protocol version: " + version); } 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 c66547fc1f..a76d34187f 100644 --- a/driver/src/main/java/org/neo4j/driver/internal/messaging/request/BeginMessage.java +++ b/driver/src/main/java/org/neo4j/driver/internal/messaging/request/BeginMessage.java @@ -25,10 +25,10 @@ import org.neo4j.driver.AccessMode; import org.neo4j.driver.Bookmark; import org.neo4j.driver.Logging; -import org.neo4j.driver.NotificationConfig; import org.neo4j.driver.TransactionConfig; import org.neo4j.driver.Value; import org.neo4j.driver.internal.DatabaseName; +import org.neo4j.driver.internal.GqlNotificationConfig; public class BeginMessage extends MessageWithMetadata { public static final byte SIGNATURE = 0x11; @@ -40,7 +40,8 @@ public BeginMessage( AccessMode mode, String impersonatedUser, String txType, - NotificationConfig notificationConfig, + GqlNotificationConfig notificationConfig, + boolean legacyNotifications, Logging logging) { this( bookmarks, @@ -51,6 +52,7 @@ public BeginMessage( impersonatedUser, txType, notificationConfig, + legacyNotifications, logging); } @@ -62,7 +64,8 @@ public BeginMessage( DatabaseName databaseName, String impersonatedUser, String txType, - NotificationConfig notificationConfig, + GqlNotificationConfig notificationConfig, + boolean legacyNotifications, Logging logging) { super(buildMetadata( txTimeout, @@ -73,6 +76,7 @@ public BeginMessage( impersonatedUser, txType, notificationConfig, + legacyNotifications, logging)); } 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 88b0b28e84..2d91a7b0a8 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 @@ -23,9 +23,9 @@ import java.util.HashMap; import java.util.Map; import java.util.Objects; -import org.neo4j.driver.NotificationConfig; import org.neo4j.driver.Value; import org.neo4j.driver.internal.BoltAgent; +import org.neo4j.driver.internal.GqlNotificationConfig; public class HelloMessage extends MessageWithMetadata { public static final byte SIGNATURE = 0x01; @@ -40,6 +40,7 @@ public class HelloMessage extends MessageWithMetadata { private static final String PATCH_BOLT_METADATA_KEY = "patch_bolt"; private static final String DATE_TIME_UTC_PATCH_VALUE = "utc"; + private static final String ENABLE_GQL_STATUS = "use_status_object"; public HelloMessage( String userAgent, @@ -47,8 +48,16 @@ public HelloMessage( Map authToken, Map routingContext, boolean includeDateTimeUtc, - NotificationConfig notificationConfig) { - super(buildMetadata(userAgent, boltAgent, authToken, routingContext, includeDateTimeUtc, notificationConfig)); + GqlNotificationConfig gqlNotificationConfig, + boolean legacyNotifications) { + super(buildMetadata( + userAgent, + boltAgent, + authToken, + routingContext, + includeDateTimeUtc, + gqlNotificationConfig, + legacyNotifications)); } @Override @@ -86,7 +95,8 @@ private static Map buildMetadata( Map authToken, Map routingContext, boolean includeDateTimeUtc, - NotificationConfig notificationConfig) { + GqlNotificationConfig notificationConfig, + boolean legacyNotifications) { Map result = new HashMap<>(authToken); if (userAgent != null) { result.put(USER_AGENT_METADATA_KEY, value(userAgent)); @@ -101,7 +111,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); + MessageWithMetadata.appendNotificationConfig(result, notificationConfig, legacyNotifications); return result; } 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 e16a9d57fd..eca850e30d 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 @@ -19,16 +19,15 @@ 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.GqlNotificationConfig; 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"; + static final String NOTIFICATIONS_DISABLED_CLASSIFICATIONS = "notifications_disabled_classifications"; private final Map metadata; public MessageWithMetadata(Map metadata) { @@ -39,23 +38,22 @@ public Map metadata() { return metadata; } - static void appendNotificationConfig(Map result, NotificationConfig config) { + static void appendNotificationConfig( + Map result, GqlNotificationConfig config, boolean legacyNotifications) { 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)); - } + var severity = (InternalNotificationSeverity) config.minimumSeverity(); + if (severity != null) { + result.put(NOTIFICATIONS_MINIMUM_SEVERITY, value(severity.type().toString())); + } + var disabledClassifications = config.disabledClassifications(); + if (disabledClassifications != null) { + var list = + disabledClassifications.stream().map(Object::toString).toList(); + result.put( + legacyNotifications + ? NOTIFICATIONS_DISABLED_CATEGORIES + : NOTIFICATIONS_DISABLED_CLASSIFICATIONS, + 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 c10dc0e28e..258781b477 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 @@ -27,11 +27,11 @@ import org.neo4j.driver.AccessMode; import org.neo4j.driver.Bookmark; import org.neo4j.driver.Logging; -import org.neo4j.driver.NotificationConfig; import org.neo4j.driver.Query; import org.neo4j.driver.TransactionConfig; import org.neo4j.driver.Value; import org.neo4j.driver.internal.DatabaseName; +import org.neo4j.driver.internal.GqlNotificationConfig; public class RunWithMetadataMessage extends MessageWithMetadata { public static final byte SIGNATURE = 0x10; @@ -46,7 +46,8 @@ public static RunWithMetadataMessage autoCommitTxRunMessage( AccessMode mode, Set bookmarks, String impersonatedUser, - NotificationConfig notificationConfig, + GqlNotificationConfig notificationConfig, + boolean legacyNotifications, Logging logging) { return autoCommitTxRunMessage( query, @@ -57,6 +58,7 @@ public static RunWithMetadataMessage autoCommitTxRunMessage( bookmarks, impersonatedUser, notificationConfig, + legacyNotifications, logging); } @@ -68,7 +70,8 @@ public static RunWithMetadataMessage autoCommitTxRunMessage( AccessMode mode, Set bookmarks, String impersonatedUser, - NotificationConfig notificationConfig, + GqlNotificationConfig notificationConfig, + boolean legacyNotifications, Logging logging) { var metadata = buildMetadata( txTimeout, @@ -79,6 +82,7 @@ public static RunWithMetadataMessage autoCommitTxRunMessage( impersonatedUser, null, notificationConfig, + legacyNotifications, logging); 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 955d17c44f..2e2df5ed6a 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 @@ -25,9 +25,9 @@ import org.neo4j.driver.AccessMode; import org.neo4j.driver.Bookmark; import org.neo4j.driver.Logging; -import org.neo4j.driver.NotificationConfig; import org.neo4j.driver.Value; import org.neo4j.driver.internal.DatabaseName; +import org.neo4j.driver.internal.GqlNotificationConfig; import org.neo4j.driver.internal.util.Iterables; public class TransactionMetadataBuilder { @@ -48,7 +48,8 @@ public static Map buildMetadata( Set bookmarks, String impersonatedUser, String txType, - NotificationConfig notificationConfig, + GqlNotificationConfig notificationConfig, + boolean legacyNotifications, Logging logging) { var bookmarksPresent = !bookmarks.isEmpty(); var txTimeoutPresent = txTimeout != null; @@ -97,7 +98,7 @@ public static Map buildMetadata( if (txTypePresent) { result.put(TX_TYPE_KEY, value(txType)); } - MessageWithMetadata.appendNotificationConfig(result, notificationConfig); + MessageWithMetadata.appendNotificationConfig(result, notificationConfig, legacyNotifications); 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 d0a16cb28f..06c778ed81 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,7 +34,6 @@ import org.neo4j.driver.AuthToken; import org.neo4j.driver.Bookmark; import org.neo4j.driver.Logging; -import org.neo4j.driver.NotificationConfig; import org.neo4j.driver.Query; import org.neo4j.driver.TransactionConfig; import org.neo4j.driver.exceptions.Neo4jException; @@ -42,6 +41,7 @@ import org.neo4j.driver.internal.BoltAgent; import org.neo4j.driver.internal.DatabaseBookmark; import org.neo4j.driver.internal.DatabaseName; +import org.neo4j.driver.internal.GqlNotificationConfig; import org.neo4j.driver.internal.async.UnmanagedTransaction; import org.neo4j.driver.internal.cluster.RoutingContext; import org.neo4j.driver.internal.cursor.AsyncResultCursorOnlyFactory; @@ -83,7 +83,7 @@ public void initializeChannel( AuthToken authToken, RoutingContext routingContext, ChannelPromise channelInitializedPromise, - NotificationConfig notificationConfig, + GqlNotificationConfig notificationConfig, Clock clock) { var exception = verifyNotificationConfigSupported(notificationConfig); if (exception != null) { @@ -100,7 +100,8 @@ public void initializeChannel( ((InternalAuthToken) authToken).toMap(), routingContext.toMap(), includeDateTimeUtcPatchInHello(), - notificationConfig); + notificationConfig, + useLegacyNotifications()); } else { message = new HelloMessage( userAgent, @@ -108,7 +109,8 @@ public void initializeChannel( ((InternalAuthToken) authToken).toMap(), null, includeDateTimeUtcPatchInHello(), - notificationConfig); + notificationConfig, + useLegacyNotifications()); } var handler = new HelloResponseHandler(channelInitializedPromise, clock); @@ -134,7 +136,7 @@ public CompletionStage beginTransaction( Set bookmarks, TransactionConfig config, String txType, - NotificationConfig notificationConfig, + GqlNotificationConfig notificationConfig, Logging logging, boolean flush) { var exception = verifyNotificationConfigSupported(notificationConfig); @@ -156,6 +158,7 @@ public CompletionStage beginTransaction( connection.impersonatedUser(), txType, notificationConfig, + useLegacyNotifications(), logging); var handler = new BeginTxResponseHandler(beginTxFuture); if (flush) { @@ -188,7 +191,7 @@ public ResultCursorFactory runInAutoCommitTransaction( Consumer bookmarkConsumer, TransactionConfig config, long fetchSize, - NotificationConfig notificationConfig, + GqlNotificationConfig notificationConfig, Logging logging) { var exception = verifyNotificationConfigSupported(notificationConfig); if (exception != null) { @@ -203,6 +206,7 @@ public ResultCursorFactory runInAutoCommitTransaction( bookmarks, connection.impersonatedUser(), notificationConfig, + useLegacyNotifications(), logging); return buildResultCursorFactory(connection, query, bookmarkConsumer, null, runMessage, fetchSize); } @@ -246,13 +250,17 @@ protected boolean includeDateTimeUtcPatchInHello() { return false; } - protected Neo4jException verifyNotificationConfigSupported(NotificationConfig notificationConfig) { + protected Neo4jException verifyNotificationConfigSupported(GqlNotificationConfig notificationConfig) { Neo4jException exception = null; - if (notificationConfig != null && !notificationConfig.equals(NotificationConfig.defaultConfig())) { + if (notificationConfig != null && !notificationConfig.equals(GqlNotificationConfig.defaultConfig())) { exception = new UnsupportedFeatureException(String.format( "Notification configuration is not supported on Bolt %s", version().toString())); } return exception; } + + protected boolean useLegacyNotifications() { + return true; + } } 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 index e7fed32fee..11bb0b3125 100644 --- 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 @@ -24,8 +24,8 @@ import java.util.Collections; import java.util.concurrent.CompletableFuture; import org.neo4j.driver.AuthToken; -import org.neo4j.driver.NotificationConfig; import org.neo4j.driver.internal.BoltAgent; +import org.neo4j.driver.internal.GqlNotificationConfig; import org.neo4j.driver.internal.cluster.RoutingContext; import org.neo4j.driver.internal.handlers.HelloV51ResponseHandler; import org.neo4j.driver.internal.messaging.BoltProtocol; @@ -45,7 +45,7 @@ public void initializeChannel( AuthToken authToken, RoutingContext routingContext, ChannelPromise channelInitializedPromise, - NotificationConfig notificationConfig, + GqlNotificationConfig notificationConfig, Clock clock) { var exception = verifyNotificationConfigSupported(notificationConfig); if (exception != null) { @@ -57,9 +57,16 @@ public void initializeChannel( if (routingContext.isServerRoutingEnabled()) { message = new HelloMessage( - userAgent, null, Collections.emptyMap(), routingContext.toMap(), false, notificationConfig); + userAgent, + null, + Collections.emptyMap(), + routingContext.toMap(), + false, + notificationConfig, + useLegacyNotifications()); } else { - message = new HelloMessage(userAgent, null, Collections.emptyMap(), null, false, notificationConfig); + message = new HelloMessage( + userAgent, null, Collections.emptyMap(), null, false, notificationConfig, useLegacyNotifications()); } var helloFuture = new CompletableFuture(); 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 index 64ec22681c..68cc4e9790 100644 --- 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 @@ -16,8 +16,8 @@ */ package org.neo4j.driver.internal.messaging.v52; -import org.neo4j.driver.NotificationConfig; import org.neo4j.driver.exceptions.Neo4jException; +import org.neo4j.driver.internal.GqlNotificationConfig; import org.neo4j.driver.internal.messaging.BoltProtocol; import org.neo4j.driver.internal.messaging.BoltProtocolVersion; import org.neo4j.driver.internal.messaging.v51.BoltProtocolV51; @@ -27,7 +27,7 @@ public class BoltProtocolV52 extends BoltProtocolV51 { public static final BoltProtocol INSTANCE = new BoltProtocolV52(); @Override - protected Neo4jException verifyNotificationConfigSupported(NotificationConfig notificationConfig) { + protected Neo4jException verifyNotificationConfigSupported(GqlNotificationConfig notificationConfig) { return null; } diff --git a/driver/src/main/java/org/neo4j/driver/internal/messaging/v53/BoltProtocolV53.java b/driver/src/main/java/org/neo4j/driver/internal/messaging/v53/BoltProtocolV53.java index 237d3b1b75..94bdb990ca 100644 --- a/driver/src/main/java/org/neo4j/driver/internal/messaging/v53/BoltProtocolV53.java +++ b/driver/src/main/java/org/neo4j/driver/internal/messaging/v53/BoltProtocolV53.java @@ -24,8 +24,8 @@ import java.util.Collections; import java.util.concurrent.CompletableFuture; import org.neo4j.driver.AuthToken; -import org.neo4j.driver.NotificationConfig; import org.neo4j.driver.internal.BoltAgent; +import org.neo4j.driver.internal.GqlNotificationConfig; import org.neo4j.driver.internal.cluster.RoutingContext; import org.neo4j.driver.internal.handlers.HelloV51ResponseHandler; import org.neo4j.driver.internal.messaging.BoltProtocol; @@ -44,7 +44,7 @@ public void initializeChannel( AuthToken authToken, RoutingContext routingContext, ChannelPromise channelInitializedPromise, - NotificationConfig notificationConfig, + GqlNotificationConfig notificationConfig, Clock clock) { var exception = verifyNotificationConfigSupported(notificationConfig); if (exception != null) { @@ -56,9 +56,22 @@ public void initializeChannel( if (routingContext.isServerRoutingEnabled()) { message = new HelloMessage( - userAgent, boltAgent, Collections.emptyMap(), routingContext.toMap(), false, notificationConfig); + userAgent, + boltAgent, + Collections.emptyMap(), + routingContext.toMap(), + false, + notificationConfig, + useLegacyNotifications()); } else { - message = new HelloMessage(userAgent, boltAgent, Collections.emptyMap(), null, false, notificationConfig); + message = new HelloMessage( + userAgent, + boltAgent, + Collections.emptyMap(), + null, + false, + notificationConfig, + useLegacyNotifications()); } var helloFuture = new CompletableFuture(); diff --git a/driver/src/main/java/org/neo4j/driver/internal/messaging/v55/BoltProtocolV55.java b/driver/src/main/java/org/neo4j/driver/internal/messaging/v55/BoltProtocolV55.java new file mode 100644 index 0000000000..529d34eb64 --- /dev/null +++ b/driver/src/main/java/org/neo4j/driver/internal/messaging/v55/BoltProtocolV55.java @@ -0,0 +1,36 @@ +/* + * Copyright (c) "Neo4j" + * Neo4j Sweden AB [https://neo4j.com] + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.neo4j.driver.internal.messaging.v55; + +import org.neo4j.driver.internal.messaging.BoltProtocol; +import org.neo4j.driver.internal.messaging.BoltProtocolVersion; +import org.neo4j.driver.internal.messaging.v54.BoltProtocolV54; + +public class BoltProtocolV55 extends BoltProtocolV54 { + public static final BoltProtocolVersion VERSION = new BoltProtocolVersion(5, 5); + public static final BoltProtocol INSTANCE = new BoltProtocolV55(); + + @Override + public BoltProtocolVersion version() { + return VERSION; + } + + @Override + protected boolean useLegacyNotifications() { + return false; + } +} diff --git a/driver/src/main/java/org/neo4j/driver/internal/summary/InternalGqlStatusObject.java b/driver/src/main/java/org/neo4j/driver/internal/summary/InternalGqlStatusObject.java new file mode 100644 index 0000000000..97919c2c20 --- /dev/null +++ b/driver/src/main/java/org/neo4j/driver/internal/summary/InternalGqlStatusObject.java @@ -0,0 +1,102 @@ +/* + * Copyright (c) "Neo4j" + * Neo4j Sweden AB [https://neo4j.com] + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.neo4j.driver.internal.summary; + +import java.util.Map; +import java.util.Objects; +import org.neo4j.driver.Value; +import org.neo4j.driver.Values; +import org.neo4j.driver.summary.GqlStatusObject; + +public class InternalGqlStatusObject implements GqlStatusObject { + public static final GqlStatusObject SUCCESS = new InternalGqlStatusObject( + "00000", + "note: successful completion", + Map.ofEntries( + Map.entry("CURRENT_SCHEMA", Values.value("/")), + Map.entry("OPERATION", Values.value("")), + Map.entry("OPERATION_CODE", Values.value("0")))); + public static final GqlStatusObject NO_DATA = new InternalGqlStatusObject( + "02000", + "note: no data", + Map.ofEntries( + Map.entry("CURRENT_SCHEMA", Values.value("/")), + Map.entry("OPERATION", Values.value("")), + Map.entry("OPERATION_CODE", Values.value("0")))); + public static final GqlStatusObject NO_DATA_UNKNOWN = new InternalGqlStatusObject( + "02N42", + "note: no data - unknown subcondition", + Map.ofEntries( + Map.entry("CURRENT_SCHEMA", Values.value("/")), + Map.entry("OPERATION", Values.value("")), + Map.entry("OPERATION_CODE", Values.value("0")))); + public static final GqlStatusObject OMITTED_RESULT = new InternalGqlStatusObject( + "00001", + "note: successful completion - omitted result", + Map.ofEntries( + Map.entry("CURRENT_SCHEMA", Values.value("/")), + Map.entry("OPERATION", Values.value("")), + Map.entry("OPERATION_CODE", Values.value("0")))); + + private final String gqlStatus; + private final String statusDescription; + private final Map diagnosticRecord; + + public InternalGqlStatusObject(String gqlStatus, String statusDescription, Map diagnosticRecord) { + this.gqlStatus = Objects.requireNonNull(gqlStatus); + this.statusDescription = Objects.requireNonNull(statusDescription); + this.diagnosticRecord = Objects.requireNonNull(diagnosticRecord); + } + + @Override + public String gqlStatus() { + return gqlStatus; + } + + @Override + public String statusDescription() { + return statusDescription; + } + + @Override + public Map diagnosticRecord() { + return diagnosticRecord; + } + + @Override + public boolean equals(Object o) { + if (this == o) return true; + if (o == null || getClass() != o.getClass()) return false; + var that = (InternalGqlStatusObject) o; + return Objects.equals(gqlStatus, that.gqlStatus) + && Objects.equals(statusDescription, that.statusDescription) + && Objects.equals(diagnosticRecord, that.diagnosticRecord); + } + + @Override + public int hashCode() { + return Objects.hash(gqlStatus, statusDescription, diagnosticRecord); + } + + @Override + public String toString() { + return "InternalGqlStatusObject{" + "gqlStatus='" + + gqlStatus + '\'' + ", statusDescription='" + + statusDescription + '\'' + ", diagnosticRecord=" + + diagnosticRecord + '}'; + } +} 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 a6fc565b4a..b48f93cb83 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 @@ -16,86 +16,85 @@ */ package org.neo4j.driver.internal.summary; -import static org.neo4j.driver.internal.value.NullValue.NULL; - +import java.util.Arrays; +import java.util.Map; +import java.util.Objects; import java.util.Optional; -import java.util.function.Function; import org.neo4j.driver.NotificationCategory; +import org.neo4j.driver.NotificationClassification; 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 = value -> { - var code = value.get("code").asString(); - var title = value.get("title").asString(); - var 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); - - var 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); - }; +public class InternalNotification extends InternalGqlStatusObject implements Notification { + public static Optional valueOf(String value) { + return Arrays.stream(NotificationClassification.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 SECURITY -> NotificationCategory.SECURITY; + case TOPOLOGY -> NotificationCategory.TOPOLOGY; + case GENERIC -> NotificationCategory.GENERIC; + }); + } private final String code; private final String title; private final String description; private final NotificationSeverity severityLevel; private final String rawSeverityLevel; - private final NotificationCategory category; - private final String rawCategory; + private final NotificationClassification classification; + private final String rawClassification; private final InputPosition position; public InternalNotification( + String gqlStatus, + String statusDescription, + Map diagnosticRecord, String code, String title, String description, NotificationSeverity severityLevel, String rawSeverityLevel, - NotificationCategory category, - String rawCategory, + NotificationClassification classification, + String rawClassification, InputPosition position) { - this.code = code; + super(gqlStatus, statusDescription, diagnosticRecord); + this.code = Objects.requireNonNull(code); this.title = title; this.description = description; this.severityLevel = severityLevel; this.rawSeverityLevel = rawSeverityLevel; - this.category = category; - this.rawCategory = rawCategory; + this.classification = classification; + this.rawClassification = rawClassification; this.position = position; } + @SuppressWarnings({"deprecation", "RedundantSuppression"}) @Override public String code() { return code; } + @SuppressWarnings({"deprecation", "RedundantSuppression"}) @Override public String title() { return title; } + @SuppressWarnings({"deprecation", "RedundantSuppression"}) @Override public String description() { return description; } + @SuppressWarnings({"deprecation", "RedundantSuppression"}) @Override public InputPosition position() { return position; @@ -111,21 +110,62 @@ public Optional rawSeverityLevel() { return Optional.ofNullable(rawSeverityLevel); } + @Override + public Optional classification() { + return Optional.ofNullable(classification); + } + + @Override + public Optional rawClassification() { + return Optional.ofNullable(rawClassification); + } + @Override public Optional category() { - return Optional.ofNullable(category); + return Optional.ofNullable(classification); } @Override public Optional rawCategory() { - return Optional.ofNullable(rawCategory); + return Optional.ofNullable(rawClassification); + } + + @Override + public boolean equals(Object o) { + if (this == o) return true; + if (o == null || getClass() != o.getClass()) return false; + if (!super.equals(o)) return false; + var that = (InternalNotification) o; + return Objects.equals(code, that.code) + && Objects.equals(title, that.title) + && Objects.equals(description, that.description) + && Objects.equals(severityLevel, that.severityLevel) + && Objects.equals(rawSeverityLevel, that.rawSeverityLevel) + && classification == that.classification + && Objects.equals(rawClassification, that.rawClassification) + && Objects.equals(position, that.position); + } + + @Override + public int hashCode() { + return Objects.hash( + super.hashCode(), + code, + title, + description, + severityLevel, + rawSeverityLevel, + classification, + rawClassification, + position); } @Override public String toString() { var info = "code=" + code + ", title=" + title + ", description=" + description + ", severityLevel=" - + severityLevel + ", rawSeverityLevel=" + rawSeverityLevel + ", category=" + category + ", rawCategory=" - + rawCategory; + + severityLevel + ", rawSeverityLevel=" + rawSeverityLevel + ", classification=" + classification + + ", rawClassification=" + + rawClassification; return position == null ? info : info + ", position={" + position + "}"; } } diff --git a/driver/src/main/java/org/neo4j/driver/internal/summary/InternalResultSummary.java b/driver/src/main/java/org/neo4j/driver/internal/summary/InternalResultSummary.java index d82e65a1a7..96955167c6 100644 --- a/driver/src/main/java/org/neo4j/driver/internal/summary/InternalResultSummary.java +++ b/driver/src/main/java/org/neo4j/driver/internal/summary/InternalResultSummary.java @@ -16,12 +16,13 @@ */ package org.neo4j.driver.internal.summary; -import java.util.Collections; import java.util.List; import java.util.Objects; +import java.util.Set; import java.util.concurrent.TimeUnit; import org.neo4j.driver.Query; import org.neo4j.driver.summary.DatabaseInfo; +import org.neo4j.driver.summary.GqlStatusObject; import org.neo4j.driver.summary.Notification; import org.neo4j.driver.summary.Plan; import org.neo4j.driver.summary.ProfiledPlan; @@ -38,6 +39,7 @@ public class InternalResultSummary implements ResultSummary { private final Plan plan; private final ProfiledPlan profile; private final List notifications; + private final Set gqlStatusObjects; private final long resultAvailableAfter; private final long resultConsumedAfter; private final DatabaseInfo databaseInfo; @@ -51,6 +53,7 @@ public InternalResultSummary( Plan plan, ProfiledPlan profile, List notifications, + Set gqlStatusObjects, long resultAvailableAfter, long resultConsumedAfter) { this.query = query; @@ -60,7 +63,8 @@ public InternalResultSummary( this.counters = counters; this.plan = resolvePlan(plan, profile); this.profile = profile; - this.notifications = notifications; + this.notifications = Objects.requireNonNull(notifications); + this.gqlStatusObjects = Objects.requireNonNull(gqlStatusObjects); this.resultAvailableAfter = resultAvailableAfter; this.resultConsumedAfter = resultConsumedAfter; } @@ -102,7 +106,12 @@ public ProfiledPlan profile() { @Override public List notifications() { - return notifications == null ? Collections.emptyList() : notifications; + return notifications; + } + + @Override + public Set gqlStatusObjects() { + return gqlStatusObjects; } @Override @@ -146,7 +155,7 @@ public boolean equals(Object o) { && Objects.equals(counters, that.counters) && Objects.equals(plan, that.plan) && Objects.equals(profile, that.profile) - && Objects.equals(notifications, that.notifications); + && Objects.equals(gqlStatusObjects, that.gqlStatusObjects); } @Override @@ -158,7 +167,7 @@ public int hashCode() { counters, plan, profile, - notifications, + gqlStatusObjects, resultAvailableAfter, resultConsumedAfter); } @@ -172,8 +181,8 @@ public String toString() { + queryType + ", counters=" + counters + ", plan=" + plan + ", profile=" - + profile + ", notifications=" - + notifications + ", resultAvailableAfter=" + + profile + ", gqlStatusObjects=" + + gqlStatusObjects + ", resultAvailableAfter=" + resultAvailableAfter + ", resultConsumedAfter=" + resultConsumedAfter + '}'; } diff --git a/driver/src/main/java/org/neo4j/driver/internal/util/MetadataExtractor.java b/driver/src/main/java/org/neo4j/driver/internal/util/MetadataExtractor.java index 2efaeff17d..27ef64cfe2 100644 --- a/driver/src/main/java/org/neo4j/driver/internal/util/MetadataExtractor.java +++ b/driver/src/main/java/org/neo4j/driver/internal/util/MetadataExtractor.java @@ -16,24 +16,43 @@ */ package org.neo4j.driver.internal.util; +import static java.util.Collections.unmodifiableSet; +import static java.util.stream.Collectors.collectingAndThen; +import static java.util.stream.Collectors.teeing; +import static java.util.stream.Collectors.toCollection; +import static java.util.stream.Collectors.toUnmodifiableList; import static org.neo4j.driver.internal.summary.InternalDatabaseInfo.DEFAULT_DATABASE_INFO; import static org.neo4j.driver.internal.types.InternalTypeSystem.TYPE_SYSTEM; +import static org.neo4j.driver.internal.value.NullValue.NULL; import java.util.Collections; +import java.util.Comparator; +import java.util.HashMap; import java.util.HashSet; +import java.util.LinkedHashSet; import java.util.List; import java.util.Map; +import java.util.OptionalInt; import java.util.Set; +import java.util.TreeSet; import java.util.function.Function; +import java.util.stream.Stream; +import java.util.stream.StreamSupport; import org.neo4j.driver.Bookmark; +import org.neo4j.driver.NotificationClassification; +import org.neo4j.driver.NotificationSeverity; import org.neo4j.driver.Query; import org.neo4j.driver.Value; +import org.neo4j.driver.Values; import org.neo4j.driver.exceptions.ProtocolException; import org.neo4j.driver.exceptions.UntrustedServerException; import org.neo4j.driver.internal.DatabaseBookmark; import org.neo4j.driver.internal.InternalBookmark; +import org.neo4j.driver.internal.InternalNotificationSeverity; import org.neo4j.driver.internal.spi.Connection; import org.neo4j.driver.internal.summary.InternalDatabaseInfo; +import org.neo4j.driver.internal.summary.InternalGqlStatusObject; +import org.neo4j.driver.internal.summary.InternalInputPosition; import org.neo4j.driver.internal.summary.InternalNotification; import org.neo4j.driver.internal.summary.InternalPlan; import org.neo4j.driver.internal.summary.InternalProfiledPlan; @@ -41,18 +60,37 @@ import org.neo4j.driver.internal.summary.InternalServerInfo; import org.neo4j.driver.internal.summary.InternalSummaryCounters; import org.neo4j.driver.summary.DatabaseInfo; +import org.neo4j.driver.summary.GqlStatusObject; +import org.neo4j.driver.summary.InputPosition; import org.neo4j.driver.summary.Notification; import org.neo4j.driver.summary.Plan; import org.neo4j.driver.summary.ProfiledPlan; import org.neo4j.driver.summary.QueryType; import org.neo4j.driver.summary.ResultSummary; import org.neo4j.driver.summary.ServerInfo; +import org.neo4j.driver.types.MapAccessor; +import org.neo4j.driver.types.TypeSystem; public class MetadataExtractor { public static final int ABSENT_QUERY_ID = -1; private static final String UNEXPECTED_TYPE_MSG_FMT = "Unexpected query type '%s', consider updating the driver"; private static final Function UNEXPECTED_TYPE_EXCEPTION_SUPPLIER = (type) -> new ProtocolException(String.format(UNEXPECTED_TYPE_MSG_FMT, type)); + private static final Comparator GQL_STATUS_OBJECT_COMPARATOR = + Comparator.comparingInt(gqlStatusObject -> { + var status = gqlStatusObject.gqlStatus(); + if (status.startsWith("02")) { + return 0; + } else if (status.startsWith("01")) { + return 1; + } else if (status.startsWith("00")) { + return 2; + } else if (status.startsWith("03")) { + return 3; + } else { + return 4; + } + }); private final String resultAvailableAfterMetadataKey; private final String resultConsumedAfterMetadataKey; @@ -93,12 +131,44 @@ public long extractResultAvailableAfter(Map metadata) { } public ResultSummary extractSummary( - Query query, Connection connection, long resultAvailableAfter, Map metadata) { + Query query, + Connection connection, + long resultAvailableAfter, + Map metadata, + boolean legacyNotifications, + GqlStatusObject gqlStatusObject) { ServerInfo serverInfo = new InternalServerInfo( connection.serverAgent(), connection.serverAddress(), connection.protocol().version()); var dbInfo = extractDatabaseInfo(metadata); + Set gqlStatusObjects; + List notifications; + if (legacyNotifications) { + var gqlStatusObjectsAndNotifications = extractGqlStatusObjectsFromNotifications(metadata) + .collect(teeing( + collectingAndThen( + toCollection( + () -> (Set) new TreeSet<>(GQL_STATUS_OBJECT_COMPARATOR)), + set -> { + if (gqlStatusObject != null) { + set.add(gqlStatusObject); + } + return unmodifiableSet(set); + }), + toUnmodifiableList(), + GqlStatusObjectsAndNotifications::new)); + gqlStatusObjects = gqlStatusObjectsAndNotifications.gqlStatusObjects(); + notifications = gqlStatusObjectsAndNotifications.notifications(); + } else { + gqlStatusObjects = extractGqlStatusObjects(metadata) + .collect(collectingAndThen( + toCollection(() -> (Set) new LinkedHashSet()), + Collections::unmodifiableSet)); + notifications = gqlStatusObjects.stream() + .flatMap(status -> status instanceof Notification ? Stream.of((Notification) status) : null) + .toList(); + } return new InternalResultSummary( query, serverInfo, @@ -107,7 +177,8 @@ public ResultSummary extractSummary( extractCounters(metadata), extractPlan(metadata), extractProfiledPlan(metadata), - extractNotifications(metadata), + notifications, + gqlStatusObjects, resultAvailableAfter, extractResultConsumedAfter(metadata, resultConsumedAfterMetadataKey)); } @@ -198,12 +269,180 @@ private static ProfiledPlan extractProfiledPlan(Map metadata) { return null; } - private static List extractNotifications(Map metadata) { + private static Stream extractGqlStatusObjectsFromNotifications(Map metadata) { var notificationsValue = metadata.get("notifications"); - if (notificationsValue != null) { - return notificationsValue.asList(InternalNotification.VALUE_TO_NOTIFICATION); + if (notificationsValue != null && TypeSystem.getDefault().LIST().isTypeOf(notificationsValue)) { + var iterable = notificationsValue.values(value -> { + var code = value.get("code").asString(); + var title = value.get("title").asString(); + var 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 = InternalNotification.valueOf(rawCategory).orElse(null); + + var 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()); + } + + var gqlStatusCode = "03N42"; + var gqlStatusDescription = description; + if (NotificationSeverity.WARNING.equals(severityLevel)) { + gqlStatusCode = "01N42"; + if (gqlStatusDescription == null || "null".equals(gqlStatusDescription)) { + gqlStatusDescription = "warn: unknown warning"; + } + } else { + if (gqlStatusDescription == null || "null".equals(gqlStatusDescription)) { + gqlStatusDescription = "info: unknown notification"; + } + } + + var diagnosticRecord = new HashMap(3); + diagnosticRecord.put("OPERATION", Values.value("")); + diagnosticRecord.put("OPERATION_CODE", Values.value("0")); + diagnosticRecord.put("CURRENT_SCHEMA", Values.value("/")); + if (rawSeverityLevel != null) { + diagnosticRecord.put("_severity", Values.value(rawSeverityLevel)); + } + if (rawCategory != null) { + diagnosticRecord.put("_classification", Values.value(rawCategory)); + } + if (position != null) { + diagnosticRecord.put( + "_position", + Values.value(Map.of( + "offset", + Values.value(position.offset()), + "line", + Values.value(position.line()), + "column", + Values.value(position.column())))); + } + + return new InternalNotification( + gqlStatusCode, + gqlStatusDescription, + Collections.unmodifiableMap(diagnosticRecord), + code, + title, + description, + severityLevel, + rawSeverityLevel, + (NotificationClassification) category, + rawCategory, + position); + }); + return StreamSupport.stream(iterable.spliterator(), false).map(Notification.class::cast); + } else { + return Stream.empty(); + } + } + + private static Stream extractGqlStatusObjects(Map metadata) { + var statuses = metadata.get("statuses"); + if (statuses != null && TypeSystem.getDefault().LIST().isTypeOf(statuses)) { + var iterable = statuses.values(MetadataExtractor::extractGqlStatusObject); + return StreamSupport.stream(iterable.spliterator(), false); + } else { + return Stream.empty(); + } + } + + private static GqlStatusObject extractGqlStatusObject(Value value) { + var status = value.get("gql_status").asString(); + var description = value.get("status_description").asString(); + Map diagnosticRecord; + var diagnosticRecordValue = value.get("diagnostic_record"); + if (diagnosticRecordValue != null && TypeSystem.getDefault().MAP().isTypeOf(diagnosticRecordValue)) { + var containsOperation = diagnosticRecordValue.containsKey("OPERATION"); + var containsOperationCode = diagnosticRecordValue.containsKey("OPERATION_CODE"); + var containsCurrentSchema = diagnosticRecordValue.containsKey("CURRENT_SCHEMA"); + if (containsOperation && containsOperationCode && containsCurrentSchema) { + diagnosticRecord = diagnosticRecordValue.asMap(Values::value); + } else { + diagnosticRecord = new HashMap<>(diagnosticRecordValue.asMap(Values::value)); + if (!containsOperation) { + diagnosticRecord.put("OPERATION", Values.value("")); + } + if (!containsOperationCode) { + diagnosticRecord.put("OPERATION_CODE", Values.value("0")); + } + if (!containsCurrentSchema) { + diagnosticRecord.put("CURRENT_SCHEMA", Values.value("/")); + } + diagnosticRecord = Collections.unmodifiableMap(diagnosticRecord); + } + } else { + diagnosticRecord = Map.ofEntries( + Map.entry("OPERATION", Values.value("")), + Map.entry("OPERATION_CODE", Values.value("0")), + Map.entry("CURRENT_SCHEMA", Values.value("/"))); + } + + var neo4jCode = value.get("neo4j_code").asString(null); + + if (neo4jCode == null || neo4jCode.trim().isEmpty()) { + return new InternalGqlStatusObject(status, description, diagnosticRecord); + } else { + var title = value.get("title").asString(); + + var positionValue = diagnosticRecord.get("_position"); + InputPosition position = null; + if (positionValue != null && TypeSystem.getDefault().MAP().isTypeOf(positionValue)) { + var offset = getAsInt(positionValue, "offset"); + var line = getAsInt(positionValue, "line"); + var column = getAsInt(positionValue, "column"); + if (Stream.of(offset, line, column).allMatch(OptionalInt::isPresent)) { + position = new InternalInputPosition(offset.getAsInt(), line.getAsInt(), column.getAsInt()); + } + } + + var severityValue = diagnosticRecord.get("_severity"); + String rawSeverity = null; + if (severityValue != null && TypeSystem.getDefault().STRING().isTypeOf(severityValue)) { + rawSeverity = severityValue.asString(); + } + var severity = InternalNotificationSeverity.valueOf(rawSeverity).orElse(null); + + var classificationValue = diagnosticRecord.get("_classification"); + String rawClassification = null; + if (classificationValue != null && TypeSystem.getDefault().STRING().isTypeOf(classificationValue)) { + rawClassification = classificationValue.asString(); + } + var classification = (NotificationClassification) + InternalNotification.valueOf(rawClassification).orElse(null); + + return new InternalNotification( + status, + description, + diagnosticRecord, + neo4jCode, + title, + description, + severity, + rawSeverity, + classification, + rawClassification, + position); + } + } + + private static OptionalInt getAsInt(MapAccessor mapAccessor, String key) { + var value = mapAccessor.get(key); + if (value != null && TypeSystem.getDefault().INTEGER().isTypeOf(value)) { + return OptionalInt.of(value.asInt()); + } else { + return OptionalInt.empty(); } - return Collections.emptyList(); } private static long extractResultConsumedAfter(Map metadata, String key) { @@ -222,4 +461,7 @@ public static Set extractBoltPatches(Map metadata) { return Collections.emptySet(); } } + + private record GqlStatusObjectsAndNotifications( + Set gqlStatusObjects, List notifications) {} } diff --git a/driver/src/main/java/org/neo4j/driver/summary/GqlStatusObject.java b/driver/src/main/java/org/neo4j/driver/summary/GqlStatusObject.java new file mode 100644 index 0000000000..272aeb4cfc --- /dev/null +++ b/driver/src/main/java/org/neo4j/driver/summary/GqlStatusObject.java @@ -0,0 +1,47 @@ +/* + * Copyright (c) "Neo4j" + * Neo4j Sweden AB [https://neo4j.com] + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.neo4j.driver.summary; + +import java.util.Map; +import org.neo4j.driver.Value; +import org.neo4j.driver.util.Preview; + +/** + * The GQL-status object as defined by the GQL standard. + * @since 5.22.0 + * @see Notification Notification subtype of the GQL-status object + */ +@Preview(name = "GQL-status object") +public interface GqlStatusObject { + /** + * Returns the GQLSTATUS as defined by the GQL standard. + * @return the GQLSTATUS value + */ + String gqlStatus(); + + /** + * The GQLSTATUS description. + * @return the GQLSTATUS description + */ + String statusDescription(); + + /** + * Returns the diagnostic record. + * @return the diagnostic record + */ + Map diagnosticRecord(); +} 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 f3249eac0a..66c5a7ee61 100644 --- a/driver/src/main/java/org/neo4j/driver/summary/Notification.java +++ b/driver/src/main/java/org/neo4j/driver/summary/Notification.java @@ -18,8 +18,10 @@ import java.util.Optional; import org.neo4j.driver.NotificationCategory; +import org.neo4j.driver.NotificationClassification; import org.neo4j.driver.NotificationSeverity; import org.neo4j.driver.util.Immutable; +import org.neo4j.driver.util.Preview; /** * Representation for notifications found when executing a query. @@ -28,7 +30,7 @@ * @since 1.0 */ @Immutable -public interface Notification { +public interface Notification extends GqlStatusObject { /** * Returns a notification code for the discovered issue. * @return the notification code @@ -38,6 +40,7 @@ public interface Notification { /** * Returns a short summary of the notification. * @return the title of the notification. + * @see #gqlStatus() */ String title(); @@ -56,6 +59,19 @@ public interface Notification { */ InputPosition position(); + /** + * Returns a position in the query where this notification points to. + *

+ * Not all notifications have a unique position to point to and in that case an empty {@link Optional} is returned. + * + * @return an {@link Optional} of the {@link InputPosition} if available or an empty {@link Optional} otherwise + * @since 5.22.0 + */ + @Preview(name = "GQL-status object") + default Optional inputPosition() { + return Optional.ofNullable(position()); + } + /** * The severity level of the notification. * @@ -68,25 +84,52 @@ default String severity() { } /** - * Returns the severity level of the notification. + * Returns the severity level of the notification derived from the diagnostic record. * * @return the severity level of the notification * @since 5.7 + * @see #diagnosticRecord() */ default Optional severityLevel() { return Optional.empty(); } /** - * Returns the raw severity level of the notification as a String returned by the server. + * Returns the raw severity level of the notification as a String value retrieved directly from the diagnostic + * record. * * @return the severity level of the notification * @since 5.7 + * @see #diagnosticRecord() */ default Optional rawSeverityLevel() { return Optional.empty(); } + /** + * Returns {@link NotificationClassification} derived from the diagnostic record. + * @return an {@link Optional} of {@link NotificationClassification} or an empty {@link Optional} when the + * classification is either absent or unrecognised + * @since 5.22.0 + * @see #diagnosticRecord() + */ + @Preview(name = "GQL-status object") + default Optional classification() { + return Optional.empty(); + } + + /** + * Returns notification classification from the diagnostic record as a {@link String} value retrieved directly from + * the diagnostic record. + * @return an {@link Optional} of notification classification or an empty {@link Optional} when it is absent + * @since 5.22.0 + * @see #diagnosticRecord() + */ + @Preview(name = "GQL-status object") + default Optional rawClassification() { + return Optional.empty(); + } + /** * Returns the category of the notification. * diff --git a/driver/src/main/java/org/neo4j/driver/summary/ResultSummary.java b/driver/src/main/java/org/neo4j/driver/summary/ResultSummary.java index f32d952112..fbe183e93d 100644 --- a/driver/src/main/java/org/neo4j/driver/summary/ResultSummary.java +++ b/driver/src/main/java/org/neo4j/driver/summary/ResultSummary.java @@ -17,9 +17,11 @@ package org.neo4j.driver.summary; import java.util.List; +import java.util.Set; import java.util.concurrent.TimeUnit; import org.neo4j.driver.Query; import org.neo4j.driver.util.Immutable; +import org.neo4j.driver.util.Preview; /** * The result summary of running a query. The result summary interface can be used to investigate @@ -29,6 +31,7 @@ * The result summary is only available after all result records have been consumed. *

* Keeping the result summary around does not influence the lifecycle of any associated session and/or transaction. + * * @since 1.0 */ @Immutable @@ -82,12 +85,25 @@ public interface ResultSummary { * in a client. *

* Unlike failures or errors, notifications do not affect the execution of a query. + *

+ * Since {@link Notification} is a subtype of {@link GqlStatusObject}, the list of notifications is a subset of all + * GQL-status objects that are of {@link Notification} type. However, the order might be different. * * @return a list of notifications produced while executing the query. The list will be empty if no * notifications produced while executing the query. + * @see #gqlStatusObjects() */ List notifications(); + /** + * Returns a sequenced set of GQL-status objects resulting from the request execution. + * + * @return the sequenced set of GQL-status objects + * @since 5.22.0 + */ + @Preview(name = "GQL-status object") + Set gqlStatusObjects(); + /** * The time it took the server to make the result available for consumption. * @@ -106,12 +122,14 @@ public interface ResultSummary { /** * The basic information of the server where the result is obtained from + * * @return basic information of the server where the result is obtained from */ ServerInfo server(); /** * The basic information of the database where the result is obtained from + * * @return the basic information of the database where the result is obtained from */ DatabaseInfo database(); diff --git a/driver/src/main/java/org/neo4j/driver/util/Preview.java b/driver/src/main/java/org/neo4j/driver/util/Preview.java index 23f4a52629..3202775c5a 100644 --- a/driver/src/main/java/org/neo4j/driver/util/Preview.java +++ b/driver/src/main/java/org/neo4j/driver/util/Preview.java @@ -41,7 +41,7 @@ @Inherited @Retention(RetentionPolicy.RUNTIME) @Documented -@Target({ElementType.TYPE, ElementType.METHOD}) +@Target({ElementType.TYPE, ElementType.METHOD, ElementType.FIELD}) public @interface Preview { /** * The feature name or a reference. diff --git a/driver/src/test/java/org/neo4j/driver/ConfigTest.java b/driver/src/test/java/org/neo4j/driver/ConfigTest.java index 64773d17e7..2aed0268d6 100644 --- a/driver/src/test/java/org/neo4j/driver/ConfigTest.java +++ b/driver/src/test/java/org/neo4j/driver/ConfigTest.java @@ -529,4 +529,38 @@ void shouldChangeTelemetryDisabled(boolean disabled) { // Then assertEquals(disabled, telemetryDisabled); } + + @Test + void shouldNotHaveMinimumNotificationSeverity() { + var config = Config.defaultConfig(); + + assertTrue(config.minimumNotificationSeverity().isEmpty()); + } + + @Test + void shouldSetMinimumNotificationSeverity() { + var config = Config.builder() + .withMinimumNotificationSeverity(NotificationSeverity.WARNING) + .build(); + + assertEquals( + NotificationSeverity.WARNING, + config.minimumNotificationSeverity().orElse(null)); + } + + @Test + void shouldNotHaveDisabledNotificationClassifications() { + var config = Config.defaultConfig(); + + assertTrue(config.disabledNotificationClassifications().isEmpty()); + } + + @Test + void shouldSetDisabledNotificationClassifications() { + var config = Config.builder() + .withDisabledNotificationClassifications(Set.of(NotificationClassification.SECURITY)) + .build(); + + assertEquals(Set.of(NotificationClassification.SECURITY), config.disabledNotificationClassifications()); + } } diff --git a/driver/src/test/java/org/neo4j/driver/SessionConfigTest.java b/driver/src/test/java/org/neo4j/driver/SessionConfigTest.java index 5dc2ed6548..7808933c38 100644 --- a/driver/src/test/java/org/neo4j/driver/SessionConfigTest.java +++ b/driver/src/test/java/org/neo4j/driver/SessionConfigTest.java @@ -231,4 +231,38 @@ void shouldSerialize() throws Exception { .disableCategories(Set.of(NotificationCategory.UNSUPPORTED, NotificationCategory.UNRECOGNIZED)), config.notificationConfig()); } + + @Test + void shouldNotHaveMinimumNotificationSeverity() { + var config = builder().build(); + + assertTrue(config.minimumNotificationSeverity().isEmpty()); + } + + @Test + void shouldSetMinimumNotificationSeverity() { + var config = Config.builder() + .withMinimumNotificationSeverity(NotificationSeverity.WARNING) + .build(); + + assertEquals( + NotificationSeverity.WARNING, + config.minimumNotificationSeverity().orElse(null)); + } + + @Test + void shouldNotHaveDisabledNotificationClassifications() { + var config = builder().build(); + + assertTrue(config.disabledNotificationClassifications().isEmpty()); + } + + @Test + void shouldSetDisabledNotificationClassifications() { + var config = Config.builder() + .withDisabledNotificationClassifications(Set.of(NotificationClassification.SECURITY)) + .build(); + + assertEquals(Set.of(NotificationClassification.SECURITY), config.disabledNotificationClassifications()); + } } diff --git a/driver/src/test/java/org/neo4j/driver/internal/InternalNotificationCategoryTests.java b/driver/src/test/java/org/neo4j/driver/internal/InternalNotificationCategoryTests.java deleted file mode 100644 index cb6df0bd5b..0000000000 --- a/driver/src/test/java/org/neo4j/driver/internal/InternalNotificationCategoryTests.java +++ /dev/null @@ -1,73 +0,0 @@ -/* - * Copyright (c) "Neo4j" - * Neo4j Sweden AB [https://neo4j.com] - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ -package org.neo4j.driver.internal; - -import static org.junit.jupiter.api.Assertions.assertEquals; -import static org.junit.jupiter.api.Assertions.assertTrue; - -import java.util.Arrays; -import java.util.stream.Stream; -import org.junit.jupiter.api.Named; -import org.junit.jupiter.api.Test; -import org.junit.jupiter.params.ParameterizedTest; -import org.junit.jupiter.params.provider.Arguments; -import org.junit.jupiter.params.provider.MethodSource; -import org.neo4j.driver.NotificationCategory; - -class InternalNotificationCategoryTests { - - @ParameterizedTest - @MethodSource("typeToCategoryMappings") - void parseKnownCategories(TypeAndCategory typeAndCategory) { - var parsedValue = InternalNotificationCategory.valueOf(typeAndCategory.type()); - - assertTrue(parsedValue.isPresent()); - assertEquals(typeAndCategory.category(), parsedValue.get()); - } - - private static Stream typeToCategoryMappings() { - return Arrays.stream(InternalNotificationCategory.Type.values()).map(type -> switch (type) { - case HINT -> Arguments.of( - Named.of(type.toString(), new TypeAndCategory(type.toString(), NotificationCategory.HINT))); - case UNRECOGNIZED -> Arguments.of( - Named.of(type.toString(), new TypeAndCategory(type.toString(), NotificationCategory.UNRECOGNIZED))); - case UNSUPPORTED -> Arguments.of( - Named.of(type.toString(), new TypeAndCategory(type.toString(), NotificationCategory.UNSUPPORTED))); - case PERFORMANCE -> Arguments.of( - Named.of(type.toString(), new TypeAndCategory(type.toString(), NotificationCategory.PERFORMANCE))); - case DEPRECATION -> Arguments.of( - Named.of(type.toString(), new TypeAndCategory(type.toString(), NotificationCategory.DEPRECATION))); - case SECURITY -> Arguments.of( - Named.of(type.toString(), new TypeAndCategory(type.toString(), NotificationCategory.SECURITY))); - case TOPOLOGY -> Arguments.of( - Named.of(type.toString(), new TypeAndCategory(type.toString(), NotificationCategory.TOPOLOGY))); - case GENERIC -> Arguments.of( - Named.of(type.toString(), new TypeAndCategory(type.toString(), NotificationCategory.GENERIC))); - }); - } - - private record TypeAndCategory(String type, NotificationCategory category) {} - - @Test - void shouldReturnEmptyWhenNoMatchFound() { - var unknownCategory = "something"; - - var parsedValue = InternalNotificationCategory.valueOf(unknownCategory); - - System.out.println(parsedValue); - } -} diff --git a/driver/src/test/java/org/neo4j/driver/internal/async/AsyncResultCursorImplTest.java b/driver/src/test/java/org/neo4j/driver/internal/async/AsyncResultCursorImplTest.java index 300199fc88..1dc38f2de0 100644 --- a/driver/src/test/java/org/neo4j/driver/internal/async/AsyncResultCursorImplTest.java +++ b/driver/src/test/java/org/neo4j/driver/internal/async/AsyncResultCursorImplTest.java @@ -37,6 +37,7 @@ import static org.neo4j.driver.internal.util.Futures.failedFuture; import static org.neo4j.driver.testutil.TestUtil.await; +import java.util.Collections; import java.util.List; import java.util.concurrent.CompletableFuture; import java.util.concurrent.CopyOnWriteArrayList; @@ -88,6 +89,7 @@ void shouldReturnSummary() { null, null, emptyList(), + Collections.emptySet(), 42, 42); when(pullAllHandler.consumeAsync()).thenReturn(completedFuture(summary)); 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 a7dd582f35..e8513b72da 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 @@ -30,7 +30,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.v54.BoltProtocolV54; +import org.neo4j.driver.internal.messaging.v55.BoltProtocolV55; class BoltProtocolUtilTest { @Test @@ -38,7 +38,7 @@ void shouldReturnHandshakeBuf() { assertByteBufContains( handshakeBuf(), BOLT_MAGIC_PREAMBLE, - (4 << 16) | BoltProtocolV54.VERSION.toInt(), + (5 << 16) | BoltProtocolV55.VERSION.toInt(), (2 << 16) | BoltProtocolV44.VERSION.toInt(), BoltProtocolV41.VERSION.toInt(), BoltProtocolV3.VERSION.toInt()); @@ -46,7 +46,7 @@ void shouldReturnHandshakeBuf() { @Test void shouldReturnHandshakeString() { - assertEquals("[0x6060b017, 263173, 132100, 260, 3]", handshakeString()); + assertEquals("[0x6060b017, 328965, 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 8d093dd279..116984d584 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 @@ -93,7 +93,7 @@ void shouldWriteInitializationMessageInBoltV3WhenHandshakeCompleted() { given(authContext.getAuthTokenManager()).willReturn(authTokenManager); setAuthContext(channel, authContext); testWritingOfInitializationMessage( - new HelloMessage(USER_AGENT, null, authToken().toMap(), Collections.emptyMap(), false, null)); + new HelloMessage(USER_AGENT, null, authToken().toMap(), Collections.emptyMap(), false, null, true)); then(authContext).should().initiateAuth(authToken); } diff --git a/driver/src/test/java/org/neo4j/driver/internal/handlers/pulln/SessionPullResponseCompletionListenerTest.java b/driver/src/test/java/org/neo4j/driver/internal/handlers/pulln/SessionPullResponseCompletionListenerTest.java index 66eb4441d5..bfb0e4fef3 100644 --- a/driver/src/test/java/org/neo4j/driver/internal/handlers/pulln/SessionPullResponseCompletionListenerTest.java +++ b/driver/src/test/java/org/neo4j/driver/internal/handlers/pulln/SessionPullResponseCompletionListenerTest.java @@ -20,6 +20,7 @@ import static org.hamcrest.MatcherAssert.assertThat; import static org.mockito.ArgumentMatchers.any; import static org.mockito.ArgumentMatchers.eq; +import static org.mockito.BDDMockito.given; import static org.mockito.Mockito.mock; import static org.mockito.Mockito.verify; @@ -33,6 +34,7 @@ import org.neo4j.driver.internal.handlers.SessionPullResponseCompletionListener; import org.neo4j.driver.internal.messaging.v4.BoltProtocolV4; import org.neo4j.driver.internal.spi.Connection; +import org.neo4j.driver.internal.util.QueryKeys; import org.neo4j.driver.summary.ResultSummary; class SessionPullResponseCompletionListenerTest extends BasicPullResponseHandlerTestBase { @@ -95,6 +97,7 @@ private static BasicPullResponseHandler newSessionResponseHandler( Consumer bookmarkConsumer, BasicPullResponseHandler.State state) { var runHandler = mock(RunResponseHandler.class); + given(runHandler.queryKeys()).willReturn(new QueryKeys(Collections.emptyList())); var listener = new SessionPullResponseCompletionListener(conn, bookmarkConsumer); var handler = new BasicPullResponseHandler( mock(Query.class), runHandler, conn, BoltProtocolV4.METADATA_EXTRACTOR, listener); diff --git a/driver/src/test/java/org/neo4j/driver/internal/handlers/pulln/TransactionPullResponseCompletionListenerTest.java b/driver/src/test/java/org/neo4j/driver/internal/handlers/pulln/TransactionPullResponseCompletionListenerTest.java index 862a559558..9be536c25d 100644 --- a/driver/src/test/java/org/neo4j/driver/internal/handlers/pulln/TransactionPullResponseCompletionListenerTest.java +++ b/driver/src/test/java/org/neo4j/driver/internal/handlers/pulln/TransactionPullResponseCompletionListenerTest.java @@ -20,6 +20,7 @@ import static org.hamcrest.MatcherAssert.assertThat; import static org.mockito.ArgumentMatchers.any; import static org.mockito.ArgumentMatchers.eq; +import static org.mockito.BDDMockito.given; import static org.mockito.Mockito.mock; import static org.mockito.Mockito.verify; import static org.mockito.Mockito.when; @@ -33,6 +34,7 @@ import org.neo4j.driver.internal.handlers.TransactionPullResponseCompletionListener; import org.neo4j.driver.internal.messaging.v4.BoltProtocolV4; import org.neo4j.driver.internal.spi.Connection; +import org.neo4j.driver.internal.util.QueryKeys; import org.neo4j.driver.summary.ResultSummary; public class TransactionPullResponseCompletionListenerTest extends BasicPullResponseHandlerTestBase { @@ -96,6 +98,7 @@ private static BasicPullResponseHandler newTxResponseHandler( UnmanagedTransaction tx, BasicPullResponseHandler.State state) { var runHandler = mock(RunResponseHandler.class); + given(runHandler.queryKeys()).willReturn(new QueryKeys(Collections.emptyList())); var listener = new TransactionPullResponseCompletionListener(tx); var handler = new BasicPullResponseHandler( mock(Query.class), runHandler, conn, BoltProtocolV4.METADATA_EXTRACTOR, listener); 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 551e78afee..00a4c573fc 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 @@ -68,6 +68,7 @@ void shouldEncodeBeginMessage(AccessMode mode, String impersonatedUser, String t impersonatedUser, txType, null, + true, Logging.none()), 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 91736c1af1..74db38ddc2 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 @@ -40,7 +40,7 @@ void shouldEncodeHelloMessage() throws Exception { authToken.put("username", value("bob")); authToken.put("password", value("secret")); - encoder.encode(new HelloMessage("MyDriver", BoltAgentUtil.VALUE, authToken, null, false, null), packer); + encoder.encode(new HelloMessage("MyDriver", BoltAgentUtil.VALUE, authToken, null, false, null, true), packer); var order = inOrder(packer); order.verify(packer).packStructHeader(1, HelloMessage.SIGNATURE); @@ -61,7 +61,8 @@ void shouldEncodeHelloMessageWithRoutingContext() throws Exception { routingContext.put("policy", "eu-fast"); encoder.encode( - new HelloMessage("MyDriver", BoltAgentUtil.VALUE, authToken, routingContext, false, null), packer); + new HelloMessage("MyDriver", BoltAgentUtil.VALUE, authToken, routingContext, false, null, true), + packer); var 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 3a321d5406..637f732458 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 @@ -64,7 +64,16 @@ void shouldEncodeRunWithMetadataMessage(AccessMode mode) throws Exception { var query = new Query("RETURN $answer", value(params)); encoder.encode( autoCommitTxRunMessage( - query, txTimeout, txMetadata, defaultDatabase(), mode, bookmarks, null, null, Logging.none()), + query, + txTimeout, + txMetadata, + defaultDatabase(), + mode, + bookmarks, + null, + null, + true, + Logging.none()), packer); var order = inOrder(packer); 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 c7421051f9..0bed53c98b 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,8 +39,8 @@ void shouldHaveCorrectMetadata() { authToken.put("user", value("Alice")); authToken.put("credentials", value("SecretPassword")); - var message = - new HelloMessage("MyDriver/1.0.2", BoltAgentUtil.VALUE, authToken, Collections.emptyMap(), false, null); + var message = new HelloMessage( + "MyDriver/1.0.2", BoltAgentUtil.VALUE, authToken, Collections.emptyMap(), false, null, true); Map expectedMetadata = new HashMap<>(authToken); expectedMetadata.put("user_agent", value("MyDriver/1.0.2")); @@ -59,7 +59,8 @@ void shouldHaveCorrectRoutingContext() { routingContext.put("region", "China"); routingContext.put("speed", "Slow"); - var message = new HelloMessage("MyDriver/1.0.2", BoltAgentUtil.VALUE, authToken, routingContext, false, null); + var message = + new HelloMessage("MyDriver/1.0.2", BoltAgentUtil.VALUE, authToken, routingContext, false, null, true); Map expectedMetadata = new HashMap<>(authToken); expectedMetadata.put("user_agent", value("MyDriver/1.0.2")); @@ -74,8 +75,8 @@ void shouldNotExposeCredentialsInToString() { authToken.put(PRINCIPAL_KEY, value("Alice")); authToken.put(CREDENTIALS_KEY, value("SecretPassword")); - var message = - new HelloMessage("MyDriver/1.0.2", BoltAgentUtil.VALUE, authToken, Collections.emptyMap(), false, null); + var message = new HelloMessage( + "MyDriver/1.0.2", BoltAgentUtil.VALUE, authToken, Collections.emptyMap(), false, null, true); assertThat(message.toString(), not(containsString("SecretPassword"))); } @@ -86,7 +87,7 @@ void shouldAcceptNullBoltAgent() { authToken.put("user", value("Alice")); authToken.put("credentials", value("SecretPassword")); - var message = new HelloMessage("MyDriver/1.0.2", null, authToken, Collections.emptyMap(), false, null); + var message = new HelloMessage("MyDriver/1.0.2", null, authToken, Collections.emptyMap(), false, null, true); var expectedMetadata = new HashMap<>(authToken); expectedMetadata.put("user_agent", value("MyDriver/1.0.2")); @@ -101,7 +102,8 @@ void shouldAcceptDetailedBoltAgent() { authToken.put("credentials", value("SecretPassword")); var boltAgent = new BoltAgent("1", "2", "3", "4"); - var message = new HelloMessage("MyDriver/1.0.2", boltAgent, authToken, Collections.emptyMap(), false, null); + var message = + new HelloMessage("MyDriver/1.0.2", boltAgent, authToken, Collections.emptyMap(), false, null, true); var expectedMetadata = new HashMap<>(authToken); expectedMetadata.put("user_agent", value("MyDriver/1.0.2")); 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 1323943f29..86a5806e15 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 @@ -45,10 +45,10 @@ import org.neo4j.driver.Bookmark; import org.neo4j.driver.Logger; import org.neo4j.driver.Logging; -import org.neo4j.driver.NotificationCategory; -import org.neo4j.driver.NotificationConfig; +import org.neo4j.driver.NotificationClassification; import org.neo4j.driver.NotificationSeverity; import org.neo4j.driver.Value; +import org.neo4j.driver.internal.GqlNotificationConfig; import org.neo4j.driver.internal.InternalBookmark; public class TransactionMetadataBuilderTest { @@ -66,7 +66,7 @@ void shouldHaveCorrectMetadata(AccessMode mode) { var txTimeout = Duration.ofSeconds(7); var metadata = buildMetadata( - txTimeout, txMetadata, defaultDatabase(), mode, bookmarks, null, null, null, Logging.none()); + txTimeout, txMetadata, defaultDatabase(), mode, bookmarks, null, null, null, true, Logging.none()); Map expectedMetadata = new HashMap<>(); expectedMetadata.put( @@ -94,7 +94,16 @@ void shouldHaveCorrectMetadataForDatabaseName(String databaseName) { var txTimeout = Duration.ofSeconds(7); var metadata = buildMetadata( - txTimeout, txMetadata, database(databaseName), WRITE, bookmarks, null, null, null, Logging.none()); + txTimeout, + txMetadata, + database(databaseName), + WRITE, + bookmarks, + null, + null, + null, + true, + Logging.none()); Map expectedMetadata = new HashMap<>(); expectedMetadata.put( @@ -109,12 +118,12 @@ void shouldHaveCorrectMetadataForDatabaseName(String databaseName) { @Test void shouldNotHaveMetadataForDatabaseNameWhenIsNull() { var metadata = buildMetadata( - null, null, defaultDatabase(), WRITE, Collections.emptySet(), null, null, null, Logging.none()); + null, null, defaultDatabase(), WRITE, Collections.emptySet(), null, null, null, true, Logging.none()); assertTrue(metadata.isEmpty()); } @Test - void shouldIncludeNotificationConfig() { + void shouldIncludeGqlNotificationConfig() { var metadata = buildMetadata( null, null, @@ -123,9 +132,8 @@ void shouldIncludeNotificationConfig() { Collections.emptySet(), null, null, - NotificationConfig.defaultConfig() - .enableMinimumSeverity(NotificationSeverity.WARNING) - .disableCategories(Set.of(NotificationCategory.UNSUPPORTED)), + new GqlNotificationConfig(NotificationSeverity.WARNING, Set.of(NotificationClassification.UNSUPPORTED)), + true, Logging.none()); var expectedMetadata = new HashMap(); @@ -152,6 +160,7 @@ void shouldRoundUpFractionalTimeoutAndLog(long nanosValue) { null, null, null, + true, logging); // then @@ -184,6 +193,7 @@ void shouldNotLogWhenRoundingDoesNotHappen() { null, null, null, + true, logging); // then 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 65fb78c6ab..09080adbc5 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 @@ -207,6 +207,7 @@ void shouldBeginTransactionWithoutBookmark() { null, null, null, + true, Logging.none())), any(BeginTxResponseHandler.class)); assertNull(await(stage)); @@ -230,6 +231,7 @@ void shouldBeginTransactionWithBookmarks() { null, null, null, + true, Logging.none())), any(BeginTxResponseHandler.class)); assertNull(await(stage)); @@ -252,6 +254,7 @@ void shouldBeginTransactionWithConfig() { null, null, null, + true, Logging.none())), any(BeginTxResponseHandler.class)); assertNull(await(stage)); @@ -267,7 +270,7 @@ void shouldBeginTransactionWithBookmarksAndConfig() { verify(connection) .writeAndFlush( eq(new BeginMessage( - bookmarks, txConfig, defaultDatabase(), WRITE, null, null, null, Logging.none())), + bookmarks, txConfig, defaultDatabase(), WRITE, null, null, null, true, Logging.none())), any(BeginTxResponseHandler.class)); assertNull(await(stage)); } @@ -583,7 +586,7 @@ private static ResponseHandlers verifyRunInvoked( RunWithMetadataMessage expectedMessage; if (session) { expectedMessage = RunWithMetadataMessage.autoCommitTxRunMessage( - QUERY, config, defaultDatabase(), mode, bookmarks, null, null, Logging.none()); + QUERY, config, defaultDatabase(), mode, bookmarks, null, null, true, Logging.none()); } 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 3b7bc3191e..16ccc29162 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 @@ -102,7 +102,8 @@ protected Stream supportedMessages() { ((InternalAuthToken) basic("neo4j", "neo4j")).toMap(), Collections.emptyMap(), false, - null), + null, + true), GOODBYE, new BeginMessage( Collections.singleton(InternalBookmark.parse("neo4j:bookmark:v1:tx123")), @@ -113,6 +114,7 @@ protected Stream supportedMessages() { null, null, null, + true, Logging.none()), new BeginMessage( Collections.singleton(InternalBookmark.parse("neo4j:bookmark:v1:tx123")), @@ -123,6 +125,7 @@ protected Stream supportedMessages() { null, null, null, + true, Logging.none()), COMMIT, ROLLBACK, @@ -135,6 +138,7 @@ protected Stream supportedMessages() { Collections.singleton(InternalBookmark.parse("neo4j:bookmark:v1:tx1")), null, null, + true, Logging.none()), autoCommitTxRunMessage( new Query("RETURN 1"), @@ -145,6 +149,7 @@ protected Stream supportedMessages() { Collections.singleton(InternalBookmark.parse("neo4j:bookmark:v1:tx1")), null, null, + true, Logging.none()), unmanagedTxRunMessage(new Query("RETURN 1")), PULL_ALL, @@ -161,6 +166,7 @@ protected Stream supportedMessages() { Collections.emptySet(), null, null, + true, Logging.none()), autoCommitTxRunMessage( new Query("RETURN $x", singletonMap("x", value(ZonedDateTime.now()))), @@ -171,6 +177,7 @@ protected Stream supportedMessages() { Collections.emptySet(), null, null, + true, Logging.none()), 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 52a6ba121e..c199e067ad 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 @@ -200,6 +200,7 @@ void shouldBeginTransactionWithoutBookmark() { null, null, null, + true, Logging.none())), any(BeginTxResponseHandler.class)); assertNull(await(stage)); @@ -223,6 +224,7 @@ void shouldBeginTransactionWithBookmarks() { null, null, null, + true, Logging.none())), any(BeginTxResponseHandler.class)); assertNull(await(stage)); @@ -245,6 +247,7 @@ void shouldBeginTransactionWithConfig() { null, null, null, + true, Logging.none())), any(BeginTxResponseHandler.class)); assertNull(await(stage)); @@ -260,7 +263,7 @@ void shouldBeginTransactionWithBookmarksAndConfig() { verify(connection) .writeAndFlush( eq(new BeginMessage( - bookmarks, txConfig, defaultDatabase(), WRITE, null, null, null, Logging.none())), + bookmarks, txConfig, defaultDatabase(), WRITE, null, null, null, true, Logging.none())), any(BeginTxResponseHandler.class)); assertNull(await(stage)); } @@ -581,7 +584,7 @@ private ResponseHandler verifySessionRunInvoked( AccessMode mode, DatabaseName databaseName) { var runMessage = RunWithMetadataMessage.autoCommitTxRunMessage( - QUERY, config, databaseName, mode, bookmarks, null, null, Logging.none()); + QUERY, config, databaseName, mode, bookmarks, null, null, true, Logging.none()); return verifyRunInvoked(connection, runMessage); } @@ -601,8 +604,8 @@ private ResponseHandler verifyRunInvoked(Connection connection, RunWithMetadataM private void verifyBeginInvoked( Connection connection, Set bookmarks, TransactionConfig config, DatabaseName databaseName) { var beginHandlerCaptor = ArgumentCaptor.forClass(ResponseHandler.class); - var beginMessage = - new BeginMessage(bookmarks, config, databaseName, AccessMode.WRITE, null, null, null, Logging.none()); + var beginMessage = new BeginMessage( + bookmarks, config, databaseName, AccessMode.WRITE, null, null, null, true, Logging.none()); 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 5e7660192f..017bba3aa8 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 @@ -110,7 +110,8 @@ protected Stream supportedMessages() { ((InternalAuthToken) basic("neo4j", "neo4j")).toMap(), Collections.emptyMap(), false, - null), + null, + true), GOODBYE, new BeginMessage( Collections.singleton(InternalBookmark.parse("neo4j:bookmark:v1:tx123")), @@ -121,6 +122,7 @@ protected Stream supportedMessages() { null, null, null, + true, Logging.none()), new BeginMessage( Collections.singleton(InternalBookmark.parse("neo4j:bookmark:v1:tx123")), @@ -131,6 +133,7 @@ protected Stream supportedMessages() { null, null, null, + true, Logging.none()), COMMIT, ROLLBACK, @@ -144,6 +147,7 @@ protected Stream supportedMessages() { Collections.singleton(InternalBookmark.parse("neo4j:bookmark:v1:tx1")), null, null, + true, Logging.none()), autoCommitTxRunMessage( new Query("RETURN 1"), @@ -154,6 +158,7 @@ protected Stream supportedMessages() { Collections.singleton(InternalBookmark.parse("neo4j:bookmark:v1:tx1")), null, null, + true, Logging.none()), unmanagedTxRunMessage(new Query("RETURN 1")), @@ -167,6 +172,7 @@ protected Stream supportedMessages() { Collections.emptySet(), null, null, + true, Logging.none()), autoCommitTxRunMessage( new Query("RETURN $x", singletonMap("x", value(ZonedDateTime.now()))), @@ -177,6 +183,7 @@ protected Stream supportedMessages() { Collections.emptySet(), null, null, + true, Logging.none()), 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 b0e5d2ff09..f81a02e6d5 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 @@ -205,6 +205,7 @@ void shouldBeginTransactionWithoutBookmark() { null, null, null, + true, Logging.none())), any(BeginTxResponseHandler.class)); assertNull(await(stage)); @@ -228,6 +229,7 @@ void shouldBeginTransactionWithBookmarks() { null, null, null, + true, Logging.none())), any(BeginTxResponseHandler.class)); assertNull(await(stage)); @@ -250,6 +252,7 @@ void shouldBeginTransactionWithConfig() { null, null, null, + true, Logging.none())), any(BeginTxResponseHandler.class)); assertNull(await(stage)); @@ -265,7 +268,7 @@ void shouldBeginTransactionWithBookmarksAndConfig() { verify(connection) .writeAndFlush( eq(new BeginMessage( - bookmarks, txConfig, defaultDatabase(), WRITE, null, null, null, Logging.none())), + bookmarks, txConfig, defaultDatabase(), WRITE, null, null, null, true, Logging.none())), any(BeginTxResponseHandler.class)); assertNull(await(stage)); } @@ -577,7 +580,7 @@ private ResponseHandler verifySessionRunInvoked( AccessMode mode, DatabaseName databaseName) { var runMessage = RunWithMetadataMessage.autoCommitTxRunMessage( - QUERY, config, databaseName, mode, bookmarks, null, null, Logging.none()); + QUERY, config, databaseName, mode, bookmarks, null, null, true, Logging.none()); return verifyRunInvoked(connection, runMessage); } @@ -597,8 +600,8 @@ private ResponseHandler verifyRunInvoked(Connection connection, RunWithMetadataM private void verifyBeginInvoked( Connection connection, Set bookmarks, TransactionConfig config, DatabaseName databaseName) { var beginHandlerCaptor = ArgumentCaptor.forClass(ResponseHandler.class); - var beginMessage = - new BeginMessage(bookmarks, config, databaseName, AccessMode.WRITE, null, null, null, Logging.none()); + var beginMessage = new BeginMessage( + bookmarks, config, databaseName, AccessMode.WRITE, null, null, null, true, Logging.none()); 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 6d42281b22..7eb8ce186d 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 @@ -109,7 +109,8 @@ protected Stream supportedMessages() { ((InternalAuthToken) basic("neo4j", "neo4j")).toMap(), Collections.emptyMap(), false, - null), + null, + true), GOODBYE, new BeginMessage( Collections.singleton(InternalBookmark.parse("neo4j:bookmark:v1:tx123")), @@ -120,6 +121,7 @@ protected Stream supportedMessages() { null, null, null, + true, Logging.none()), new BeginMessage( Collections.singleton(InternalBookmark.parse("neo4j:bookmark:v1:tx123")), @@ -130,6 +132,7 @@ protected Stream supportedMessages() { null, null, null, + true, Logging.none()), COMMIT, ROLLBACK, @@ -143,6 +146,7 @@ protected Stream supportedMessages() { Collections.singleton(InternalBookmark.parse("neo4j:bookmark:v1:tx1")), null, null, + true, Logging.none()), autoCommitTxRunMessage( new Query("RETURN 1"), @@ -153,6 +157,7 @@ protected Stream supportedMessages() { Collections.singleton(InternalBookmark.parse("neo4j:bookmark:v1:tx1")), null, null, + true, Logging.none()), unmanagedTxRunMessage(new Query("RETURN 1")), @@ -166,6 +171,7 @@ protected Stream supportedMessages() { Collections.emptySet(), null, null, + true, Logging.none()), autoCommitTxRunMessage( new Query("RETURN $x", singletonMap("x", value(ZonedDateTime.now()))), @@ -176,6 +182,7 @@ protected Stream supportedMessages() { Collections.emptySet(), null, null, + true, Logging.none()), 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 3ad79fb776..93a9617e1c 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 @@ -205,6 +205,7 @@ void shouldBeginTransactionWithoutBookmark() { null, null, null, + true, Logging.none())), any(BeginTxResponseHandler.class)); assertNull(await(stage)); @@ -228,6 +229,7 @@ void shouldBeginTransactionWithBookmarks() { null, null, null, + true, Logging.none())), any(BeginTxResponseHandler.class)); assertNull(await(stage)); @@ -250,6 +252,7 @@ void shouldBeginTransactionWithConfig() { null, null, null, + true, Logging.none())), any(BeginTxResponseHandler.class)); assertNull(await(stage)); @@ -265,7 +268,7 @@ void shouldBeginTransactionWithBookmarksAndConfig() { verify(connection) .writeAndFlush( eq(new BeginMessage( - bookmarks, txConfig, defaultDatabase(), WRITE, null, null, null, Logging.none())), + bookmarks, txConfig, defaultDatabase(), WRITE, null, null, null, true, Logging.none())), any(BeginTxResponseHandler.class)); assertNull(await(stage)); } @@ -578,7 +581,7 @@ private ResponseHandler verifySessionRunInvoked( AccessMode mode, DatabaseName databaseName) { var runMessage = RunWithMetadataMessage.autoCommitTxRunMessage( - QUERY, config, databaseName, mode, bookmarks, null, null, Logging.none()); + QUERY, config, databaseName, mode, bookmarks, null, null, true, Logging.none()); return verifyRunInvoked(connection, runMessage); } @@ -598,8 +601,8 @@ private ResponseHandler verifyRunInvoked(Connection connection, RunWithMetadataM private void verifyBeginInvoked( Connection connection, Set bookmarks, TransactionConfig config, DatabaseName databaseName) { var beginHandlerCaptor = ArgumentCaptor.forClass(ResponseHandler.class); - var beginMessage = - new BeginMessage(bookmarks, config, databaseName, AccessMode.WRITE, null, null, null, Logging.none()); + var beginMessage = new BeginMessage( + bookmarks, config, databaseName, AccessMode.WRITE, null, null, null, true, Logging.none()); 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 51034a07a9..656d7c8c05 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 @@ -109,7 +109,8 @@ protected Stream supportedMessages() { ((InternalAuthToken) basic("neo4j", "neo4j")).toMap(), Collections.emptyMap(), false, - null), + null, + true), GOODBYE, new BeginMessage( Collections.singleton(InternalBookmark.parse("neo4j:bookmark:v1:tx123")), @@ -120,6 +121,7 @@ protected Stream supportedMessages() { null, null, null, + true, Logging.none()), new BeginMessage( Collections.singleton(InternalBookmark.parse("neo4j:bookmark:v1:tx123")), @@ -130,6 +132,7 @@ protected Stream supportedMessages() { null, null, null, + true, Logging.none()), COMMIT, ROLLBACK, @@ -143,6 +146,7 @@ protected Stream supportedMessages() { Collections.singleton(InternalBookmark.parse("neo4j:bookmark:v1:tx1")), null, null, + true, Logging.none()), autoCommitTxRunMessage( new Query("RETURN 1"), @@ -153,6 +157,7 @@ protected Stream supportedMessages() { Collections.singleton(InternalBookmark.parse("neo4j:bookmark:v1:tx1")), null, null, + true, Logging.none()), unmanagedTxRunMessage(new Query("RETURN 1")), @@ -166,6 +171,7 @@ protected Stream supportedMessages() { Collections.emptySet(), null, null, + true, Logging.none()), autoCommitTxRunMessage( new Query("RETURN $x", singletonMap("x", value(ZonedDateTime.now()))), @@ -176,6 +182,7 @@ protected Stream supportedMessages() { Collections.emptySet(), null, null, + true, Logging.none()), 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 b12f3f3dc6..e078e7352a 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 @@ -204,6 +204,7 @@ void shouldBeginTransactionWithoutBookmark() { null, null, null, + true, Logging.none())), any(BeginTxResponseHandler.class)); assertNull(await(stage)); @@ -227,6 +228,7 @@ void shouldBeginTransactionWithBookmarks() { null, null, null, + true, Logging.none())), any(BeginTxResponseHandler.class)); assertNull(await(stage)); @@ -249,6 +251,7 @@ void shouldBeginTransactionWithConfig() { null, null, null, + true, Logging.none())), any(BeginTxResponseHandler.class)); assertNull(await(stage)); @@ -264,7 +267,7 @@ void shouldBeginTransactionWithBookmarksAndConfig() { verify(connection) .writeAndFlush( eq(new BeginMessage( - bookmarks, txConfig, defaultDatabase(), WRITE, null, null, null, Logging.none())), + bookmarks, txConfig, defaultDatabase(), WRITE, null, null, null, true, Logging.none())), any(BeginTxResponseHandler.class)); assertNull(await(stage)); } @@ -580,7 +583,7 @@ private ResponseHandler verifySessionRunInvoked( AccessMode mode, DatabaseName databaseName) { var runMessage = RunWithMetadataMessage.autoCommitTxRunMessage( - QUERY, config, databaseName, mode, bookmarks, null, null, Logging.none()); + QUERY, config, databaseName, mode, bookmarks, null, null, true, Logging.none()); return verifyRunInvoked(connection, runMessage); } @@ -600,8 +603,8 @@ private ResponseHandler verifyRunInvoked(Connection connection, RunWithMetadataM private void verifyBeginInvoked( Connection connection, Set bookmarks, TransactionConfig config, DatabaseName databaseName) { var beginHandlerCaptor = ArgumentCaptor.forClass(ResponseHandler.class); - var beginMessage = - new BeginMessage(bookmarks, config, databaseName, AccessMode.WRITE, null, null, null, Logging.none()); + var beginMessage = new BeginMessage( + bookmarks, config, databaseName, AccessMode.WRITE, null, null, null, true, Logging.none()); 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 d9a852640a..d8e13aaab6 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 @@ -114,7 +114,8 @@ protected Stream supportedMessages() { ((InternalAuthToken) basic("neo4j", "neo4j")).toMap(), Collections.emptyMap(), false, - null), + null, + true), GOODBYE, new BeginMessage( Collections.singleton(InternalBookmark.parse("neo4j:bookmark:v1:tx123")), @@ -125,6 +126,7 @@ protected Stream supportedMessages() { null, null, null, + true, Logging.none()), new BeginMessage( Collections.singleton(InternalBookmark.parse("neo4j:bookmark:v1:tx123")), @@ -135,6 +137,7 @@ protected Stream supportedMessages() { null, null, null, + true, Logging.none()), COMMIT, ROLLBACK, @@ -148,6 +151,7 @@ protected Stream supportedMessages() { Collections.singleton(InternalBookmark.parse("neo4j:bookmark:v1:tx1")), null, null, + true, Logging.none()), autoCommitTxRunMessage( new Query("RETURN 1"), @@ -158,6 +162,7 @@ protected Stream supportedMessages() { Collections.singleton(InternalBookmark.parse("neo4j:bookmark:v1:tx1")), null, null, + true, Logging.none()), unmanagedTxRunMessage(new Query("RETURN 1")), @@ -171,6 +176,7 @@ protected Stream supportedMessages() { Collections.emptySet(), null, null, + true, Logging.none()), autoCommitTxRunMessage( new Query("RETURN $x", singletonMap("x", value(ZonedDateTime.now()))), @@ -181,6 +187,7 @@ protected Stream supportedMessages() { Collections.emptySet(), null, null, + true, Logging.none()), 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 83aef37628..62e7c1cb45 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 @@ -204,6 +204,7 @@ void shouldBeginTransactionWithoutBookmark() { null, null, null, + true, Logging.none())), any(BeginTxResponseHandler.class)); assertNull(await(stage)); @@ -227,6 +228,7 @@ void shouldBeginTransactionWithBookmarks() { null, null, null, + true, Logging.none())), any(BeginTxResponseHandler.class)); assertNull(await(stage)); @@ -249,6 +251,7 @@ void shouldBeginTransactionWithConfig() { null, null, null, + true, Logging.none())), any(BeginTxResponseHandler.class)); assertNull(await(stage)); @@ -264,7 +267,7 @@ void shouldBeginTransactionWithBookmarksAndConfig() { verify(connection) .writeAndFlush( eq(new BeginMessage( - bookmarks, txConfig, defaultDatabase(), WRITE, null, null, null, Logging.none())), + bookmarks, txConfig, defaultDatabase(), WRITE, null, null, null, true, Logging.none())), any(BeginTxResponseHandler.class)); assertNull(await(stage)); } @@ -578,7 +581,7 @@ private ResponseHandler verifySessionRunInvoked( AccessMode mode, DatabaseName databaseName) { var runMessage = RunWithMetadataMessage.autoCommitTxRunMessage( - QUERY, config, databaseName, mode, bookmarks, null, null, Logging.none()); + QUERY, config, databaseName, mode, bookmarks, null, null, true, Logging.none()); return verifyRunInvoked(connection, runMessage); } @@ -598,8 +601,8 @@ private ResponseHandler verifyRunInvoked(Connection connection, RunWithMetadataM private void verifyBeginInvoked( Connection connection, Set bookmarks, TransactionConfig config, DatabaseName databaseName) { var beginHandlerCaptor = ArgumentCaptor.forClass(ResponseHandler.class); - var beginMessage = - new BeginMessage(bookmarks, config, databaseName, AccessMode.WRITE, null, null, null, Logging.none()); + var beginMessage = new BeginMessage( + bookmarks, config, databaseName, AccessMode.WRITE, null, null, null, true, Logging.none()); 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 7096fc98ee..4ba729c892 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 @@ -114,7 +114,8 @@ protected Stream supportedMessages() { ((InternalAuthToken) basic("neo4j", "neo4j")).toMap(), Collections.emptyMap(), false, - null), + null, + true), GOODBYE, new BeginMessage( Collections.singleton(InternalBookmark.parse("neo4j:bookmark:v1:tx123")), @@ -125,6 +126,7 @@ protected Stream supportedMessages() { null, null, null, + true, Logging.none()), new BeginMessage( Collections.singleton(InternalBookmark.parse("neo4j:bookmark:v1:tx123")), @@ -135,6 +137,7 @@ protected Stream supportedMessages() { null, null, null, + true, Logging.none()), COMMIT, ROLLBACK, @@ -148,6 +151,7 @@ protected Stream supportedMessages() { Collections.singleton(InternalBookmark.parse("neo4j:bookmark:v1:tx1")), null, null, + true, Logging.none()), autoCommitTxRunMessage( new Query("RETURN 1"), @@ -158,6 +162,7 @@ protected Stream supportedMessages() { Collections.singleton(InternalBookmark.parse("neo4j:bookmark:v1:tx1")), null, null, + true, Logging.none()), unmanagedTxRunMessage(new Query("RETURN 1")), @@ -171,6 +176,7 @@ protected Stream supportedMessages() { Collections.emptySet(), null, null, + true, Logging.none()), autoCommitTxRunMessage( new Query("RETURN $x", singletonMap("x", value(ZonedDateTime.now()))), @@ -181,6 +187,7 @@ protected Stream supportedMessages() { Collections.emptySet(), null, null, + true, Logging.none()), 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 be6f6874c8..e6bf61f501 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 @@ -204,6 +204,7 @@ void shouldBeginTransactionWithoutBookmark() { null, null, null, + true, Logging.none())), any(BeginTxResponseHandler.class)); assertNull(await(stage)); @@ -227,6 +228,7 @@ void shouldBeginTransactionWithBookmarks() { null, null, null, + true, Logging.none())), any(BeginTxResponseHandler.class)); assertNull(await(stage)); @@ -249,6 +251,7 @@ void shouldBeginTransactionWithConfig() { null, null, null, + true, Logging.none())), any(BeginTxResponseHandler.class)); assertNull(await(stage)); @@ -264,7 +267,7 @@ void shouldBeginTransactionWithBookmarksAndConfig() { verify(connection) .writeAndFlush( eq(new BeginMessage( - bookmarks, txConfig, defaultDatabase(), WRITE, null, null, null, Logging.none())), + bookmarks, txConfig, defaultDatabase(), WRITE, null, null, null, true, Logging.none())), any(BeginTxResponseHandler.class)); assertNull(await(stage)); } @@ -578,7 +581,7 @@ private ResponseHandler verifySessionRunInvoked( AccessMode mode, DatabaseName databaseName) { var runMessage = RunWithMetadataMessage.autoCommitTxRunMessage( - QUERY, config, databaseName, mode, bookmark, null, null, Logging.none()); + QUERY, config, databaseName, mode, bookmark, null, null, true, Logging.none()); return verifyRunInvoked(connection, runMessage); } @@ -598,8 +601,8 @@ private ResponseHandler verifyRunInvoked(Connection connection, RunWithMetadataM private void verifyBeginInvoked( Connection connection, Set bookmarks, TransactionConfig config, DatabaseName databaseName) { var beginHandlerCaptor = ArgumentCaptor.forClass(ResponseHandler.class); - var beginMessage = - new BeginMessage(bookmarks, config, databaseName, AccessMode.WRITE, null, null, null, Logging.none()); + var beginMessage = new BeginMessage( + bookmarks, config, databaseName, AccessMode.WRITE, null, null, null, true, Logging.none()); 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 e2f871f929..a472cf7fb5 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 @@ -114,7 +114,8 @@ protected Stream supportedMessages() { ((InternalAuthToken) basic("neo4j", "neo4j")).toMap(), Collections.emptyMap(), false, - null), + null, + true), GOODBYE, new BeginMessage( Collections.singleton(InternalBookmark.parse("neo4j:bookmark:v1:tx123")), @@ -125,6 +126,7 @@ protected Stream supportedMessages() { null, null, null, + true, Logging.none()), new BeginMessage( Collections.singleton(InternalBookmark.parse("neo4j:bookmark:v1:tx123")), @@ -135,6 +137,7 @@ protected Stream supportedMessages() { null, null, null, + true, Logging.none()), COMMIT, ROLLBACK, @@ -148,6 +151,7 @@ protected Stream supportedMessages() { Collections.singleton(InternalBookmark.parse("neo4j:bookmark:v1:tx1")), null, null, + true, Logging.none()), autoCommitTxRunMessage( new Query("RETURN 1"), @@ -158,6 +162,7 @@ protected Stream supportedMessages() { Collections.singleton(InternalBookmark.parse("neo4j:bookmark:v1:tx1")), null, null, + true, Logging.none()), unmanagedTxRunMessage(new Query("RETURN 1")), @@ -171,6 +176,7 @@ protected Stream supportedMessages() { Collections.emptySet(), null, null, + true, Logging.none()), autoCommitTxRunMessage( new Query("RETURN $x", singletonMap("x", value(ZonedDateTime.now()))), @@ -181,6 +187,7 @@ protected Stream supportedMessages() { Collections.emptySet(), null, null, + true, Logging.none()), 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 index d34fbdea40..72732704f7 100644 --- 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 @@ -176,6 +176,7 @@ void shouldBeginTransactionWithoutBookmark() { null, null, null, + true, Logging.none())), any(BeginTxResponseHandler.class)); assertNull(await(stage)); @@ -199,6 +200,7 @@ void shouldBeginTransactionWithBookmarks() { null, null, null, + true, Logging.none())), any(BeginTxResponseHandler.class)); assertNull(await(stage)); @@ -221,6 +223,7 @@ void shouldBeginTransactionWithConfig() { null, null, null, + true, Logging.none())), any(BeginTxResponseHandler.class)); assertNull(await(stage)); @@ -236,7 +239,7 @@ void shouldBeginTransactionWithBookmarksAndConfig() { verify(connection) .writeAndFlush( eq(new BeginMessage( - bookmarks, txConfig, defaultDatabase(), WRITE, null, null, null, Logging.none())), + bookmarks, txConfig, defaultDatabase(), WRITE, null, null, null, true, Logging.none())), any(BeginTxResponseHandler.class)); assertNull(await(stage)); } @@ -550,7 +553,7 @@ private ResponseHandler verifySessionRunInvoked( AccessMode mode, DatabaseName databaseName) { var runMessage = RunWithMetadataMessage.autoCommitTxRunMessage( - QUERY, config, databaseName, mode, bookmark, null, null, Logging.none()); + QUERY, config, databaseName, mode, bookmark, null, null, true, Logging.none()); return verifyRunInvoked(connection, runMessage); } @@ -570,8 +573,8 @@ private ResponseHandler verifyRunInvoked(Connection connection, RunWithMetadataM private void verifyBeginInvoked( Connection connection, Set bookmarks, TransactionConfig config, DatabaseName databaseName) { var beginHandlerCaptor = ArgumentCaptor.forClass(ResponseHandler.class); - var beginMessage = - new BeginMessage(bookmarks, config, databaseName, AccessMode.WRITE, null, null, null, Logging.none()); + var beginMessage = new BeginMessage( + bookmarks, config, databaseName, AccessMode.WRITE, null, null, null, true, Logging.none()); 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/v51/MessageWriterV51Test.java b/driver/src/test/java/org/neo4j/driver/internal/messaging/v51/MessageWriterV51Test.java index 75f96116ed..385e0a4ecf 100644 --- 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 @@ -109,7 +109,8 @@ protected Stream supportedMessages() { ((InternalAuthToken) basic("neo4j", "neo4j")).toMap(), Collections.emptyMap(), false, - null), + null, + true), GOODBYE, new BeginMessage( Collections.singleton(InternalBookmark.parse("neo4j:bookmark:v1:tx123")), @@ -120,6 +121,7 @@ protected Stream supportedMessages() { null, null, null, + true, Logging.none()), new BeginMessage( Collections.singleton(InternalBookmark.parse("neo4j:bookmark:v1:tx123")), @@ -130,6 +132,7 @@ protected Stream supportedMessages() { null, null, null, + true, Logging.none()), COMMIT, ROLLBACK, @@ -143,6 +146,7 @@ protected Stream supportedMessages() { Collections.singleton(InternalBookmark.parse("neo4j:bookmark:v1:tx1")), null, null, + true, Logging.none()), autoCommitTxRunMessage( new Query("RETURN 1"), @@ -153,6 +157,7 @@ protected Stream supportedMessages() { Collections.singleton(InternalBookmark.parse("neo4j:bookmark:v1:tx1")), null, null, + true, Logging.none()), unmanagedTxRunMessage(new Query("RETURN 1")), @@ -166,6 +171,7 @@ protected Stream supportedMessages() { Collections.emptySet(), null, null, + true, Logging.none()), autoCommitTxRunMessage( new Query("RETURN $x", singletonMap("x", value(ZonedDateTime.now()))), @@ -176,6 +182,7 @@ protected Stream supportedMessages() { Collections.emptySet(), null, null, + true, Logging.none()), unmanagedTxRunMessage(new Query("RETURN $x", singletonMap("x", point(42, 1, 2, 3)))), diff --git a/driver/src/test/java/org/neo4j/driver/internal/messaging/v52/BoltProtocolV52Test.java b/driver/src/test/java/org/neo4j/driver/internal/messaging/v52/BoltProtocolV52Test.java index 35b54f5dde..6f7cf193b4 100644 --- a/driver/src/test/java/org/neo4j/driver/internal/messaging/v52/BoltProtocolV52Test.java +++ b/driver/src/test/java/org/neo4j/driver/internal/messaging/v52/BoltProtocolV52Test.java @@ -177,6 +177,7 @@ void shouldBeginTransactionWithoutBookmark() { null, null, null, + true, Logging.none())), any(BeginTxResponseHandler.class)); assertNull(await(stage)); @@ -200,6 +201,7 @@ void shouldBeginTransactionWithBookmarks() { null, null, null, + true, Logging.none())), any(BeginTxResponseHandler.class)); assertNull(await(stage)); @@ -222,6 +224,7 @@ void shouldBeginTransactionWithConfig() { null, null, null, + true, Logging.none())), any(BeginTxResponseHandler.class)); assertNull(await(stage)); @@ -237,7 +240,7 @@ void shouldBeginTransactionWithBookmarksAndConfig() { verify(connection) .writeAndFlush( eq(new BeginMessage( - bookmarks, txConfig, defaultDatabase(), WRITE, null, null, null, Logging.none())), + bookmarks, txConfig, defaultDatabase(), WRITE, null, null, null, true, Logging.none())), any(BeginTxResponseHandler.class)); assertNull(await(stage)); } @@ -551,7 +554,7 @@ private ResponseHandler verifySessionRunInvoked( AccessMode mode, DatabaseName databaseName) { var runMessage = RunWithMetadataMessage.autoCommitTxRunMessage( - QUERY, config, databaseName, mode, bookmark, null, null, Logging.none()); + QUERY, config, databaseName, mode, bookmark, null, null, true, Logging.none()); return verifyRunInvoked(connection, runMessage); } @@ -571,8 +574,8 @@ private ResponseHandler verifyRunInvoked(Connection connection, RunWithMetadataM private void verifyBeginInvoked( Connection connection, Set bookmarks, TransactionConfig config, DatabaseName databaseName) { var beginHandlerCaptor = ArgumentCaptor.forClass(ResponseHandler.class); - var beginMessage = - new BeginMessage(bookmarks, config, databaseName, AccessMode.WRITE, null, null, null, Logging.none()); + var beginMessage = new BeginMessage( + bookmarks, config, databaseName, AccessMode.WRITE, null, null, null, true, Logging.none()); 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/v53/BoltProtocolV53Test.java b/driver/src/test/java/org/neo4j/driver/internal/messaging/v53/BoltProtocolV53Test.java index 46e1dd7afa..73fb0e5a86 100644 --- a/driver/src/test/java/org/neo4j/driver/internal/messaging/v53/BoltProtocolV53Test.java +++ b/driver/src/test/java/org/neo4j/driver/internal/messaging/v53/BoltProtocolV53Test.java @@ -184,6 +184,7 @@ void shouldBeginTransactionWithoutBookmark() { null, null, null, + true, Logging.none())), any(BeginTxResponseHandler.class)); assertNull(await(stage)); @@ -207,6 +208,7 @@ void shouldBeginTransactionWithBookmarks() { null, null, null, + true, Logging.none())), any(BeginTxResponseHandler.class)); assertNull(await(stage)); @@ -229,6 +231,7 @@ void shouldBeginTransactionWithConfig() { null, null, null, + true, Logging.none())), any(BeginTxResponseHandler.class)); assertNull(await(stage)); @@ -244,7 +247,7 @@ void shouldBeginTransactionWithBookmarksAndConfig() { verify(connection) .writeAndFlush( eq(new BeginMessage( - bookmarks, txConfig, defaultDatabase(), WRITE, null, null, null, Logging.none())), + bookmarks, txConfig, defaultDatabase(), WRITE, null, null, null, true, Logging.none())), any(BeginTxResponseHandler.class)); assertNull(await(stage)); } @@ -558,7 +561,7 @@ private ResponseHandler verifySessionRunInvoked( AccessMode mode, DatabaseName databaseName) { var runMessage = RunWithMetadataMessage.autoCommitTxRunMessage( - QUERY, config, databaseName, mode, bookmark, null, null, Logging.none()); + QUERY, config, databaseName, mode, bookmark, null, null, true, Logging.none()); return verifyRunInvoked(connection, runMessage); } @@ -578,8 +581,8 @@ private ResponseHandler verifyRunInvoked(Connection connection, RunWithMetadataM private void verifyBeginInvoked( Connection connection, Set bookmarks, TransactionConfig config, DatabaseName databaseName) { var beginHandlerCaptor = ArgumentCaptor.forClass(ResponseHandler.class); - var beginMessage = - new BeginMessage(bookmarks, config, databaseName, AccessMode.WRITE, null, null, null, Logging.none()); + var beginMessage = new BeginMessage( + bookmarks, config, databaseName, AccessMode.WRITE, null, null, null, true, Logging.none()); 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/v54/BoltProtocolV54Test.java b/driver/src/test/java/org/neo4j/driver/internal/messaging/v54/BoltProtocolV54Test.java index 14035d6e30..4655cc67ee 100644 --- a/driver/src/test/java/org/neo4j/driver/internal/messaging/v54/BoltProtocolV54Test.java +++ b/driver/src/test/java/org/neo4j/driver/internal/messaging/v54/BoltProtocolV54Test.java @@ -185,6 +185,7 @@ void shouldBeginTransactionWithoutBookmark() { null, null, null, + true, Logging.none())), any(BeginTxResponseHandler.class)); assertNull(await(stage)); @@ -208,6 +209,7 @@ void shouldBeginTransactionWithBookmarks() { null, null, null, + true, Logging.none())), any(BeginTxResponseHandler.class)); assertNull(await(stage)); @@ -230,6 +232,7 @@ void shouldBeginTransactionWithConfig() { null, null, null, + true, Logging.none())), any(BeginTxResponseHandler.class)); assertNull(await(stage)); @@ -245,7 +248,7 @@ void shouldBeginTransactionWithBookmarksAndConfig() { verify(connection) .writeAndFlush( eq(new BeginMessage( - bookmarks, txConfig, defaultDatabase(), WRITE, null, null, null, Logging.none())), + bookmarks, txConfig, defaultDatabase(), WRITE, null, null, null, true, Logging.none())), any(BeginTxResponseHandler.class)); assertNull(await(stage)); } @@ -567,7 +570,7 @@ private ResponseHandler verifySessionRunInvoked( AccessMode mode, DatabaseName databaseName) { var runMessage = RunWithMetadataMessage.autoCommitTxRunMessage( - QUERY, config, databaseName, mode, bookmark, null, null, Logging.none()); + QUERY, config, databaseName, mode, bookmark, null, null, true, Logging.none()); return verifyRunInvoked(connection, runMessage); } @@ -587,8 +590,8 @@ private ResponseHandler verifyRunInvoked(Connection connection, RunWithMetadataM private void verifyBeginInvoked( Connection connection, Set bookmarks, TransactionConfig config, DatabaseName databaseName) { var beginHandlerCaptor = ArgumentCaptor.forClass(ResponseHandler.class); - var beginMessage = - new BeginMessage(bookmarks, config, databaseName, AccessMode.WRITE, null, null, null, Logging.none()); + var beginMessage = new BeginMessage( + bookmarks, config, databaseName, AccessMode.WRITE, null, null, null, true, Logging.none()); 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/v54/MessageWriterV54Test.java b/driver/src/test/java/org/neo4j/driver/internal/messaging/v54/MessageWriterV54Test.java index c2fa17bcc0..129932b480 100644 --- a/driver/src/test/java/org/neo4j/driver/internal/messaging/v54/MessageWriterV54Test.java +++ b/driver/src/test/java/org/neo4j/driver/internal/messaging/v54/MessageWriterV54Test.java @@ -110,7 +110,8 @@ protected Stream supportedMessages() { ((InternalAuthToken) basic("neo4j", "neo4j")).toMap(), Collections.emptyMap(), false, - null), + null, + true), GOODBYE, new BeginMessage( Collections.singleton(InternalBookmark.parse("neo4j:bookmark:v1:tx123")), @@ -121,6 +122,7 @@ protected Stream supportedMessages() { null, null, null, + true, Logging.none()), new BeginMessage( Collections.singleton(InternalBookmark.parse("neo4j:bookmark:v1:tx123")), @@ -131,6 +133,7 @@ protected Stream supportedMessages() { null, null, null, + true, Logging.none()), COMMIT, ROLLBACK, @@ -144,6 +147,7 @@ protected Stream supportedMessages() { Collections.singleton(InternalBookmark.parse("neo4j:bookmark:v1:tx1")), null, null, + true, Logging.none()), autoCommitTxRunMessage( new Query("RETURN 1"), @@ -154,6 +158,7 @@ protected Stream supportedMessages() { Collections.singleton(InternalBookmark.parse("neo4j:bookmark:v1:tx1")), null, null, + true, Logging.none()), unmanagedTxRunMessage(new Query("RETURN 1")), @@ -167,6 +172,7 @@ protected Stream supportedMessages() { Collections.emptySet(), null, null, + true, Logging.none()), autoCommitTxRunMessage( new Query("RETURN $x", singletonMap("x", value(ZonedDateTime.now()))), @@ -177,6 +183,7 @@ protected Stream supportedMessages() { Collections.emptySet(), null, null, + true, Logging.none()), unmanagedTxRunMessage(new Query("RETURN $x", singletonMap("x", point(42, 1, 2, 3)))), diff --git a/driver/src/test/java/org/neo4j/driver/internal/reactive/util/ListBasedPullHandler.java b/driver/src/test/java/org/neo4j/driver/internal/reactive/util/ListBasedPullHandler.java index 755734af74..08d214a188 100644 --- a/driver/src/test/java/org/neo4j/driver/internal/reactive/util/ListBasedPullHandler.java +++ b/driver/src/test/java/org/neo4j/driver/internal/reactive/util/ListBasedPullHandler.java @@ -20,10 +20,12 @@ import static java.util.Collections.emptyMap; import static java.util.Collections.singletonMap; import static org.mockito.ArgumentMatchers.any; +import static org.mockito.ArgumentMatchers.anyBoolean; import static org.mockito.ArgumentMatchers.anyLong; import static org.mockito.Mockito.mock; import static org.mockito.Mockito.when; +import java.util.Collections; import java.util.List; import org.neo4j.driver.Query; import org.neo4j.driver.Record; @@ -31,6 +33,7 @@ import org.neo4j.driver.internal.handlers.PullResponseCompletionListener; import org.neo4j.driver.internal.handlers.RunResponseHandler; import org.neo4j.driver.internal.handlers.pulln.BasicPullResponseHandler; +import org.neo4j.driver.internal.messaging.v5.BoltProtocolV5; import org.neo4j.driver.internal.spi.Connection; import org.neo4j.driver.internal.util.MetadataExtractor; import org.neo4j.driver.internal.util.QueryKeys; @@ -63,12 +66,16 @@ private ListBasedPullHandler(List list, Throwable error) { mock(PullResponseCompletionListener.class)); this.list = list; this.error = error; - when(super.metadataExtractor.extractSummary(any(Query.class), any(Connection.class), anyLong(), any())) + when(super.metadataExtractor.extractSummary( + any(Query.class), any(Connection.class), anyLong(), any(), anyBoolean(), any())) .thenReturn(mock(ResultSummary.class)); if (list.size() > 1) { var record = list.get(0); when(super.runResponseHandler.queryKeys()).thenReturn(new QueryKeys(record.keys())); + } else { + when(super.runResponseHandler.queryKeys()).thenReturn(new QueryKeys(Collections.emptyList())); } + when(super.connection.protocol()).thenReturn(BoltProtocolV5.INSTANCE); } @Override 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 deleted file mode 100644 index 6c753203d0..0000000000 --- a/driver/src/test/java/org/neo4j/driver/internal/summary/InternalNotificationTest.java +++ /dev/null @@ -1,87 +0,0 @@ -/* - * Copyright (c) "Neo4j" - * Neo4j Sweden AB [https://neo4j.com] - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ -package org.neo4j.driver.internal.summary; - -import static org.hamcrest.CoreMatchers.equalTo; -import static org.hamcrest.MatcherAssert.assertThat; -import static org.hamcrest.Matchers.nullValue; - -import java.util.HashMap; -import java.util.Map; -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; -import org.neo4j.driver.internal.value.StringValue; - -class InternalNotificationTest { - @Test - @SuppressWarnings({"deprecation", "OptionalGetWithoutIsPresent"}) - void shouldHandleNotificationWithPosition() { - // GIVEN - Map map = new HashMap<>(); - map.put("description", new StringValue("A description")); - 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)); - position.put("line", new IntegerValue(2)); - map.put("position", new MapValue(position)); - Value value = new MapValue(map); - - // WHEN - var notification = InternalNotification.VALUE_TO_NOTIFICATION.apply(value); - - // THEN - assertThat(notification.description(), equalTo("A description")); - assertThat(notification.code(), equalTo("Neo.DummyNotification")); - assertThat(notification.title(), equalTo("A title")); - assertThat(notification.severity(), equalTo("WARNING")); - assertThat(notification.severityLevel().get(), equalTo(NotificationSeverity.WARNING)); - assertThat(notification.rawSeverityLevel().get(), equalTo("WARNING")); - assertThat(notification.category().get(), equalTo(NotificationCategory.DEPRECATION)); - assertThat(notification.rawCategory().get(), equalTo("DEPRECATION")); - var pos = notification.position(); - assertThat(pos.offset(), equalTo(0)); - assertThat(pos.column(), equalTo(1)); - assertThat(pos.line(), equalTo(2)); - } - - @Test - void shouldHandleNotificationWithoutPosition() { - // GIVEN - Map map = new HashMap<>(); - map.put("description", new StringValue("A description")); - map.put("code", new StringValue("Neo.DummyNotification")); - map.put("title", new StringValue("A title")); - Value value = new MapValue(map); - - // WHEN - var notification = InternalNotification.VALUE_TO_NOTIFICATION.apply(value); - - // THEN - assertThat(notification.description(), equalTo("A description")); - assertThat(notification.code(), equalTo("Neo.DummyNotification")); - assertThat(notification.title(), equalTo("A title")); - assertThat(notification.position(), nullValue()); - } -} 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 8a7f5bf714..115f660a8c 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 @@ -56,6 +56,7 @@ import org.neo4j.driver.internal.messaging.v43.BoltProtocolV43; import org.neo4j.driver.internal.spi.Connection; import org.neo4j.driver.internal.summary.InternalInputPosition; +import org.neo4j.driver.summary.Notification; import org.neo4j.driver.summary.ResultSummary; class MetadataExtractorTest { @@ -104,7 +105,7 @@ void shouldBuildResultSummaryWithQuery() { var query = new Query("UNWIND range(10, 100) AS x CREATE (:Node {name: $name, x: x})", singletonMap("name", "Apa")); - var summary = extractor.extractSummary(query, connectionMock(), 42, emptyMap()); + var summary = extractor.extractSummary(query, connectionMock(), 42, emptyMap(), true, null); assertEquals(query, summary.query()); } @@ -113,7 +114,7 @@ void shouldBuildResultSummaryWithQuery() { void shouldBuildResultSummaryWithServerAddress() { var connection = connectionMock(new BoltServerAddress("server:42")); - var summary = extractor.extractSummary(query(), connection, 42, emptyMap()); + var summary = extractor.extractSummary(query(), connection, 42, emptyMap(), true, null); assertEquals("server:42", summary.server().address()); } @@ -145,7 +146,7 @@ void shouldBuildResultSummaryWithCounters() { var metadata = singletonMap("stats", stats); - var summary = extractor.extractSummary(query(), connectionMock(), 42, metadata); + var summary = extractor.extractSummary(query(), connectionMock(), 42, metadata, true, null); assertEquals(42, summary.counters().nodesCreated()); assertEquals(4242, summary.counters().nodesDeleted()); @@ -162,24 +163,27 @@ void shouldBuildResultSummaryWithCounters() { @Test void shouldBuildResultSummaryWithoutCounters() { - var summary = extractor.extractSummary(query(), connectionMock(), 42, emptyMap()); + var summary = extractor.extractSummary(query(), connectionMock(), 42, emptyMap(), true, null); assertEquals(EMPTY_STATS, summary.counters()); } @Test void shouldBuildResultSummaryWithPlan() { var plan = value(parameters( - "operatorType", "Projection", - "args", parameters("n", 42), - "identifiers", values("a", "b"), + "operatorType", + "Projection", + "args", + parameters("n", 42), + "identifiers", + values("a", "b"), "children", - values(parameters( - "operatorType", "AllNodeScan", - "args", parameters("x", 4242), - "identifiers", values("n", "t", "f"))))); + values(parameters( + "operatorType", "AllNodeScan", + "args", parameters("x", 4242), + "identifiers", values("n", "t", "f"))))); var metadata = singletonMap("plan", plan); - var summary = extractor.extractSummary(query(), connectionMock(), 42, metadata); + var summary = extractor.extractSummary(query(), connectionMock(), 42, metadata, true, null); assertTrue(summary.hasPlan()); assertEquals("Projection", summary.plan().operatorType()); @@ -198,7 +202,7 @@ void shouldBuildResultSummaryWithPlan() { @Test void shouldBuildResultSummaryWithoutPlan() { - var summary = extractor.extractSummary(query(), connectionMock(), 42, emptyMap()); + var summary = extractor.extractSummary(query(), connectionMock(), 42, emptyMap(), true, null); assertFalse(summary.hasPlan()); assertNull(summary.plan()); } @@ -206,22 +210,28 @@ void shouldBuildResultSummaryWithoutPlan() { @Test void shouldBuildResultSummaryWithProfiledPlan() { var profile = value(parameters( - "operatorType", "ProduceResult", - "args", parameters("a", 42), - "identifiers", values("a", "b"), - "rows", value(424242), - "dbHits", value(242424), - "time", value(999), + "operatorType", + "ProduceResult", + "args", + parameters("a", 42), + "identifiers", + values("a", "b"), + "rows", + value(424242), + "dbHits", + value(242424), + "time", + value(999), "children", - values(parameters( - "operatorType", "LabelScan", - "args", parameters("x", 1), - "identifiers", values("y", "z"), - "rows", value(2), - "dbHits", value(4))))); + values(parameters( + "operatorType", "LabelScan", + "args", parameters("x", 1), + "identifiers", values("y", "z"), + "rows", value(2), + "dbHits", value(4))))); var metadata = singletonMap("profile", profile); - var summary = extractor.extractSummary(query(), connectionMock(), 42, metadata); + var summary = extractor.extractSummary(query(), connectionMock(), 42, metadata, true, null); assertTrue(summary.hasPlan()); assertTrue(summary.hasProfile()); @@ -249,7 +259,7 @@ void shouldBuildResultSummaryWithProfiledPlan() { @Test void shouldBuildResultSummaryWithoutProfiledPlan() { - var summary = extractor.extractSummary(query(), connectionMock(), 42, emptyMap()); + var summary = extractor.extractSummary(query(), connectionMock(), 42, emptyMap(), true, null); assertFalse(summary.hasProfile()); assertNull(summary.profile()); } @@ -258,30 +268,30 @@ void shouldBuildResultSummaryWithoutProfiledPlan() { @SuppressWarnings({"deprecation", "OptionalGetWithoutIsPresent"}) void shouldBuildResultSummaryWithNotifications() { var notification1 = parameters( - "description", "Almost bad thing", - "code", "Neo.DummyNotification", - "title", "A title", - "severity", "WARNING", - "category", "DEPRECATION", + "description", + "Almost bad thing", + "code", + "Neo.DummyNotification", + "title", + "A title", + "severity", + "WARNING", + "category", + "DEPRECATION", "position", - parameters( - "offset", 42, - "line", 4242, - "column", 424242)); + parameters( + "offset", 42, + "line", 4242, + "column", 424242)); var notification2 = parameters( "description", "Almost good thing", "code", "Neo.GoodNotification", "title", "Good", - "severity", "INFO", - "position", - parameters( - "offset", 1, - "line", 2, - "column", 3)); + "severity", "INFO"); var notifications = value(notification1, notification2); var metadata = singletonMap("notifications", notifications); - var summary = extractor.extractSummary(query(), connectionMock(), 42, metadata); + var summary = extractor.extractSummary(query(), connectionMock(), 42, metadata, true, null); assertEquals(2, summary.notifications().size()); var firstNotification = summary.notifications().get(0); @@ -298,17 +308,131 @@ void shouldBuildResultSummaryWithNotifications() { NotificationCategory.DEPRECATION, firstNotification.category().get()); assertEquals("DEPRECATION", firstNotification.rawCategory().get()); assertEquals(new InternalInputPosition(42, 4242, 424242), firstNotification.position()); + assertEquals( + Map.of( + "OPERATION", Values.value(""), + "OPERATION_CODE", Values.value("0"), + "CURRENT_SCHEMA", Values.value("/"), + "_severity", Values.value("WARNING"), + "_classification", Values.value("DEPRECATION"), + "_position", + parameters( + "offset", 42, + "line", 4242, + "column", 424242)), + firstNotification.diagnosticRecord()); assertEquals("Almost good thing", secondNotification.description()); assertEquals("Neo.GoodNotification", secondNotification.code()); assertEquals("Good", secondNotification.title()); assertEquals("INFO", secondNotification.severity()); - assertEquals(new InternalInputPosition(1, 2, 3), secondNotification.position()); + assertTrue(secondNotification.inputPosition().isEmpty()); + assertNull(secondNotification.position()); + assertEquals( + Map.of( + "OPERATION", Values.value(""), + "OPERATION_CODE", Values.value("0"), + "CURRENT_SCHEMA", Values.value("/"), + "_severity", Values.value("INFO")), + secondNotification.diagnosticRecord()); + + assertEquals(2, summary.gqlStatusObjects().size()); + var gqlStatusObjectsIterator = summary.gqlStatusObjects().iterator(); + var firstGqlStatusObject = (Notification) gqlStatusObjectsIterator.next(); + var secondGqlStatusObject = (Notification) gqlStatusObjectsIterator.next(); + assertEquals(firstNotification, firstGqlStatusObject); + assertEquals(secondNotification, secondGqlStatusObject); + } + + @Test + @SuppressWarnings({"deprecation", "OptionalGetWithoutIsPresent"}) + void shouldBuildResultSummaryWithGqlStatusObjects() { + var gqlStatusObject1 = parameters( + "gql_status", + "gql_status", + "status_description", + "status_description", + "neo4j_code", + "neo4j_code", + "title", + "title", + "diagnostic_record", + parameters( + "_severity", + "WARNING", + "_classification", + "SECURITY", + "_position", + parameters( + "offset", 42, + "line", 4242, + "column", 424242))); + var gqlStatusObject2 = parameters( + "gql_status", + "gql_status", + "status_description", + "status_description", + "diagnostic_record", + parameters( + "_severity", "WARNING", + "_classification", "SECURITY")); + var gqlStatusObjects = value(gqlStatusObject1, gqlStatusObject2); + var metadata = singletonMap("statuses", gqlStatusObjects); + + var summary = extractor.extractSummary(query(), connectionMock(), 42, metadata, false, null); + + assertEquals(2, summary.gqlStatusObjects().size()); + var gqlStatusObjectsIterator = summary.gqlStatusObjects().iterator(); + var firstGqlStatusObject = (Notification) gqlStatusObjectsIterator.next(); + var secondGqlStatusObject = gqlStatusObjectsIterator.next(); + + assertEquals("gql_status", firstGqlStatusObject.gqlStatus()); + assertEquals("status_description", firstGqlStatusObject.statusDescription()); + assertEquals("status_description", firstGqlStatusObject.description()); + assertEquals("neo4j_code", firstGqlStatusObject.code()); + assertEquals("title", firstGqlStatusObject.title()); + assertEquals("WARNING", firstGqlStatusObject.severity()); + assertEquals( + NotificationSeverity.WARNING, + firstGqlStatusObject.severityLevel().get()); + assertEquals("WARNING", firstGqlStatusObject.rawSeverityLevel().get()); + assertEquals( + NotificationCategory.SECURITY, firstGqlStatusObject.category().get()); + assertEquals("SECURITY", firstGqlStatusObject.rawCategory().get()); + assertEquals(new InternalInputPosition(42, 4242, 424242), firstGqlStatusObject.position()); + assertEquals( + Map.of( + "OPERATION", Values.value(""), + "OPERATION_CODE", Values.value("0"), + "CURRENT_SCHEMA", Values.value("/"), + "_severity", Values.value("WARNING"), + "_classification", Values.value("SECURITY"), + "_position", + parameters( + "offset", 42, + "line", 4242, + "column", 424242)), + firstGqlStatusObject.diagnosticRecord()); + + assertFalse(secondGqlStatusObject instanceof Notification); + assertEquals("gql_status", secondGqlStatusObject.gqlStatus()); + assertEquals("status_description", secondGqlStatusObject.statusDescription()); + assertEquals( + Map.of( + "OPERATION", Values.value(""), + "OPERATION_CODE", Values.value("0"), + "CURRENT_SCHEMA", Values.value("/"), + "_severity", Values.value("WARNING"), + "_classification", Values.value("SECURITY")), + secondGqlStatusObject.diagnosticRecord()); + + assertEquals(1, summary.notifications().size()); + assertEquals(firstGqlStatusObject, summary.notifications().get(0)); } @Test void shouldBuildResultSummaryWithoutNotifications() { - var summary = extractor.extractSummary(query(), connectionMock(), 42, emptyMap()); + var summary = extractor.extractSummary(query(), connectionMock(), 42, emptyMap(), true, null); assertEquals(0, summary.notifications().size()); } @@ -316,7 +440,7 @@ void shouldBuildResultSummaryWithoutNotifications() { void shouldBuildResultSummaryWithResultAvailableAfter() { var value = 42_000; - var summary = extractor.extractSummary(query(), connectionMock(), value, emptyMap()); + var summary = extractor.extractSummary(query(), connectionMock(), value, emptyMap(), true, null); assertEquals(42, summary.resultAvailableAfter(TimeUnit.SECONDS)); assertEquals(value, summary.resultAvailableAfter(TimeUnit.MILLISECONDS)); @@ -327,7 +451,7 @@ void shouldBuildResultSummaryWithResultConsumedAfter() { var value = 42_000; var metadata = singletonMap(RESULT_CONSUMED_AFTER_KEY, value(value)); - var summary = extractor.extractSummary(query(), connectionMock(), 42, metadata); + var summary = extractor.extractSummary(query(), connectionMock(), 42, metadata, true, null); assertEquals(42, summary.resultConsumedAfter(TimeUnit.SECONDS)); assertEquals(value, summary.resultConsumedAfter(TimeUnit.MILLISECONDS)); @@ -335,7 +459,7 @@ void shouldBuildResultSummaryWithResultConsumedAfter() { @Test void shouldBuildResultSummaryWithoutResultConsumedAfter() { - var summary = extractor.extractSummary(query(), connectionMock(), 42, emptyMap()); + var summary = extractor.extractSummary(query(), connectionMock(), 42, emptyMap(), true, null); assertEquals(-1, summary.resultConsumedAfter(TimeUnit.SECONDS)); assertEquals(-1, summary.resultConsumedAfter(TimeUnit.MILLISECONDS)); } @@ -430,7 +554,7 @@ void shouldFailToExtractServerVersionFromNonNeo4jProduct() { private ResultSummary createWithQueryType(Value typeValue) { var metadata = singletonMap("type", typeValue); - return extractor.extractSummary(query(), connectionMock(), 42, metadata); + return extractor.extractSummary(query(), connectionMock(), 42, metadata, true, null); } private static Query query() { 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 3fea70b749..2b7814d32a 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 @@ -41,6 +41,7 @@ public class GetFeatures implements TestkitRequest { "Feature:Bolt:5.2", "Feature:Bolt:5.3", "Feature:Bolt:5.4", + "Feature:Bolt:5.5", "AuthorizationExpiredTreatment", "ConfHint:connection.recv_timeout_seconds", "Feature:Auth:Bearer", @@ -69,7 +70,8 @@ public class GetFeatures implements TestkitRequest { "Feature:Auth:Managed", "Feature:API:Driver.SupportsSessionAuth", "Feature:API:RetryableExceptions", - "Feature:API:SSLClientCertificate")); + "Feature:API:SSLClientCertificate", + "Feature:API:Summary:GqlStatusObjects")); 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 bbeb178a3b..a745e7511e 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 @@ -49,12 +49,11 @@ import org.neo4j.driver.ClientCertificateManagers; import org.neo4j.driver.ClientCertificates; import org.neo4j.driver.Config; -import org.neo4j.driver.NotificationConfig; +import org.neo4j.driver.NotificationClassification; 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; @@ -101,8 +100,14 @@ public TestkitResponse process(TestkitState testkitState) { Optional.ofNullable(data.connectionAcquisitionTimeoutMs) .ifPresent(timeout -> configBuilder.withConnectionAcquisitionTimeout(timeout, TimeUnit.MILLISECONDS)); Optional.ofNullable(data.telemetryDisabled).ifPresent(configBuilder::withTelemetryDisabled); - configBuilder.withNotificationConfig( - toNotificationConfig(data.notificationsMinSeverity, data.notificationsDisabledCategories)); + Optional.ofNullable(data.notificationsMinSeverity) + .flatMap(InternalNotificationSeverity::valueOf) + .ifPresent(configBuilder::withMinimumNotificationSeverity); + Optional.ofNullable(data.notificationsDisabledCategories) + .map(categories -> categories.stream() + .map(NotificationClassification::valueOf) + .collect(Collectors.toSet())) + .ifPresent(configBuilder::withDisabledNotificationClassifications); configBuilder.withDriverMetrics(); var clientCertificateManager = Optional.ofNullable(data.getClientCertificateProviderId()) .map(testkitState::getClientCertificateManager) @@ -272,23 +277,6 @@ 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 { 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 059be648c6..4da49ed7a1 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 @@ -16,8 +16,6 @@ */ 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; @@ -39,9 +37,11 @@ import neo4j.org.testkit.backend.messages.responses.TestkitResponse; import org.neo4j.driver.AccessMode; import org.neo4j.driver.AuthToken; +import org.neo4j.driver.NotificationClassification; import org.neo4j.driver.SessionConfig; import org.neo4j.driver.async.AsyncSession; import org.neo4j.driver.internal.InternalBookmark; +import org.neo4j.driver.internal.InternalNotificationSeverity; import org.neo4j.driver.reactive.ReactiveSession; import org.neo4j.driver.reactive.RxSession; import reactor.core.publisher.Mono; @@ -107,8 +107,14 @@ private TestkitResponse createSessionStateAndResponse( .map(testkitState::getBookmarkManager) .ifPresent(builder::withBookmarkManager); - builder.withNotificationConfig( - toNotificationConfig(data.notificationsMinSeverity, data.notificationsDisabledCategories)); + Optional.ofNullable(data.notificationsMinSeverity) + .flatMap(InternalNotificationSeverity::valueOf) + .ifPresent(builder::withMinimumNotificationSeverity); + Optional.ofNullable(data.notificationsDisabledCategories) + .map(categories -> categories.stream() + .map(NotificationClassification::valueOf) + .collect(Collectors.toSet())) + .ifPresent(builder::withDisabledNotificationClassifications); var userSwitchAuthToken = data.getAuthorizationToken() != null ? AuthTokenUtil.parseAuthToken(data.getAuthorizationToken()) 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 07c7625898..d5d4ce81aa 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 @@ -18,13 +18,14 @@ import java.util.HashMap; import java.util.Map; +import java.util.Objects; import java.util.concurrent.TimeUnit; 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.Notification; import org.neo4j.driver.summary.Plan; import org.neo4j.driver.summary.ProfiledPlan; import org.neo4j.driver.summary.QueryType; @@ -72,20 +73,48 @@ public static Summary.SummaryBody toSummaryBody(org.neo4j.driver.summary.ResultS .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")) + .category(s.category().map(Objects::toString).orElse("UNKNOWN")) .rawCategory(s.rawCategory().orElse(null)) .build()) .collect(Collectors.toList()); + var gqlStatusObjects = summary.gqlStatusObjects().stream() + .map(gqlStatusObject -> { + var builder = Summary.GqlStatusObject.builder() + .gqlStatus(gqlStatusObject.gqlStatus()) + .statusDescription(gqlStatusObject.statusDescription()) + .diagnosticRecord(gqlStatusObject.diagnosticRecord()); + if (gqlStatusObject instanceof Notification notification) { + builder = builder.position(toInputPosition(notification.position())) + .severity(notification + .severityLevel() + .map(InternalNotificationSeverity.class::cast) + .map(InternalNotificationSeverity::type) + .map(InternalNotificationSeverity.Type::toString) + .orElse("UNKNOWN")) + .rawSeverity(notification.rawSeverityLevel().orElse(null)) + .classification(notification + .classification() + .map(Objects::toString) + .orElse("UNKNOWN")) + .rawClassification( + notification.rawClassification().orElse(null)) + .notification(true); + } else { + builder = builder.position(null) + .severity("UNKNOWN") + .classification("UNKNOWN") + .diagnosticRecord(gqlStatusObject.diagnosticRecord()); + } + return builder.build(); + }) + .toList(); return Summary.SummaryBody.builder() .serverInfo(serverInfo) .counters(counters) .query(query) .database(summary.database().name()) .notifications(notifications) + .gqlStatusObjects(gqlStatusObjects) .plan(toPlan(summary.plan())) .profile(toProfile(summary.profile())) .queryType(toQueryType(summary.queryType())) 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 5b584d521c..296b1e4a9f 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 @@ -16,6 +16,7 @@ */ package neo4j.org.testkit.backend.messages.responses; +import com.fasterxml.jackson.annotation.JsonProperty; import java.util.List; import java.util.Map; import lombok.Builder; @@ -46,6 +47,8 @@ public static class SummaryBody { private List notifications; + private List gqlStatusObjects; + private Plan plan; private Profile profile; @@ -129,6 +132,29 @@ public static class Notification { private String rawCategory; } + @Getter + @Builder + public static class GqlStatusObject { + private String gqlStatus; + + private String statusDescription; + + private InputPosition position; + + private String severity; + + private String rawSeverity; + + private String classification; + + private String rawClassification; + + @JsonProperty("isNotification") + private boolean notification; + + private Map diagnosticRecord; + } + @Getter @Builder public static class InputPosition { diff --git a/testkit-tests/pom.xml b/testkit-tests/pom.xml index 55a8d3280c..b7285a5cab 100644 --- a/testkit-tests/pom.xml +++ b/testkit-tests/pom.xml @@ -20,7 +20,7 @@ ${project.basedir}/.. https://github.com/neo4j-drivers/testkit.git - 5.0 + 022-gql-notifications --tests TESTKIT_TESTS INTEGRATION_TESTS STUB_TESTS STRESS_TESTS TLS_TESTS 7200000