diff --git a/README.md b/README.md index 1fbbb39084..642d12bb88 100644 --- a/README.md +++ b/README.md @@ -78,10 +78,9 @@ The `neo4j-java-driver-all` includes an explicit module declaration ([module-inf To run a simple query, the following can be used: ```java var authToken = AuthTokens.basic("neo4j", "password"); -try (var driver = GraphDatabase.driver("bolt://localhost:7687", authToken); var session = driver.session()) { - var result = session.run("CREATE (n)"); - var summary = result.consume(); - System.out.println(summary.counters().nodesCreated()); +try (var driver = GraphDatabase.driver("bolt://localhost:7687", authToken)) { + var result = driver.executableQuery("CREATE (n)").execute(); + System.out.println(result.summary().counters().nodesCreated()); } ``` diff --git a/driver/clirr-ignored-differences.xml b/driver/clirr-ignored-differences.xml index 5ea1d823ae..85002807cc 100644 --- a/driver/clirr-ignored-differences.xml +++ b/driver/clirr-ignored-differences.xml @@ -503,4 +503,39 @@ org.neo4j.driver.BookmarkManager executableQueryBookmarkManager() + + org/neo4j/driver/RoutingControl + 6001 + WRITERS + + + + org/neo4j/driver/RoutingControl + 6001 + READERS + + + + org/neo4j/driver/RoutingControl + 2000 + + + + org/neo4j/driver/RoutingControl + 4001 + java/lang/Comparable + + + + org/neo4j/driver/RoutingControl + 4001 + java/lang/constant/Constable + + + + org/neo4j/driver/RoutingControl + 5001 + java/lang/Enum + + diff --git a/driver/src/main/java/org/neo4j/driver/Driver.java b/driver/src/main/java/org/neo4j/driver/Driver.java index 25db420f9c..d0fd2bc7e6 100644 --- a/driver/src/main/java/org/neo4j/driver/Driver.java +++ b/driver/src/main/java/org/neo4j/driver/Driver.java @@ -25,7 +25,6 @@ import org.neo4j.driver.reactive.RxSession; import org.neo4j.driver.types.TypeSystem; import org.neo4j.driver.util.Experimental; -import org.neo4j.driver.util.Preview; /** * Accessor for a specific Neo4j graph database. @@ -72,7 +71,6 @@ public interface Driver extends AutoCloseable { * @return new executable query instance * @since 5.7 */ - @Preview(name = "Driver Level Queries") ExecutableQuery executableQuery(String query); /** @@ -81,7 +79,6 @@ public interface Driver extends AutoCloseable { * @return bookmark manager, must not be {@code null} * @since 5.7 */ - @Preview(name = "Driver Level Queries") BookmarkManager executableQueryBookmarkManager(); /** diff --git a/driver/src/main/java/org/neo4j/driver/EagerResult.java b/driver/src/main/java/org/neo4j/driver/EagerResult.java index c21ac6f328..5f2a23ef0b 100644 --- a/driver/src/main/java/org/neo4j/driver/EagerResult.java +++ b/driver/src/main/java/org/neo4j/driver/EagerResult.java @@ -20,13 +20,11 @@ import java.util.List; import org.neo4j.driver.summary.ResultSummary; -import org.neo4j.driver.util.Preview; /** * An in-memory result of executing a Cypher query that has been consumed in full. * @since 5.5 */ -@Preview(name = "Driver Level Queries") public interface EagerResult { /** * Returns the keys of the records this result contains. diff --git a/driver/src/main/java/org/neo4j/driver/ExecutableQuery.java b/driver/src/main/java/org/neo4j/driver/ExecutableQuery.java index 2ecce9ebe8..c6efeb5012 100644 --- a/driver/src/main/java/org/neo4j/driver/ExecutableQuery.java +++ b/driver/src/main/java/org/neo4j/driver/ExecutableQuery.java @@ -26,7 +26,6 @@ import java.util.stream.Collectors; import org.neo4j.driver.internal.EagerResultValue; import org.neo4j.driver.summary.ResultSummary; -import org.neo4j.driver.util.Preview; /** * An executable query that executes a query in a managed transaction with automatic retries on retryable errors. @@ -96,7 +95,6 @@ * * @since 5.7 */ -@Preview(name = "Driver Level Queries") public interface ExecutableQuery { /** * Sets query parameters. @@ -166,7 +164,6 @@ default T execute(Collector recordCollector) { * @param the final value type * @since 5.5 */ - @Preview(name = "Driver Level Queries") @FunctionalInterface interface ResultFinisher { /** diff --git a/driver/src/main/java/org/neo4j/driver/QueryConfig.java b/driver/src/main/java/org/neo4j/driver/QueryConfig.java index d9fd3dce8b..5a0e13b00a 100644 --- a/driver/src/main/java/org/neo4j/driver/QueryConfig.java +++ b/driver/src/main/java/org/neo4j/driver/QueryConfig.java @@ -24,13 +24,11 @@ import java.io.Serializable; import java.util.Objects; import java.util.Optional; -import org.neo4j.driver.util.Preview; /** * Query configuration used by {@link Driver#executableQuery(String)} and its variants. * @since 5.5 */ -@Preview(name = "Driver Level Queries") public final class QueryConfig implements Serializable { @Serial private static final long serialVersionUID = -2632780731598141754L; @@ -154,7 +152,7 @@ public String toString() { * Builder used to configure {@link QueryConfig} which will be used to execute a query. */ public static final class Builder { - private RoutingControl routing = RoutingControl.WRITERS; + private RoutingControl routing = RoutingControl.WRITE; private String database; private String impersonatedUser; private BookmarkManager bookmarkManager; diff --git a/driver/src/main/java/org/neo4j/driver/RoutingControl.java b/driver/src/main/java/org/neo4j/driver/RoutingControl.java index b203bc06b4..709e21cefe 100644 --- a/driver/src/main/java/org/neo4j/driver/RoutingControl.java +++ b/driver/src/main/java/org/neo4j/driver/RoutingControl.java @@ -18,20 +18,22 @@ */ package org.neo4j.driver; -import org.neo4j.driver.util.Preview; +import java.io.Serializable; +import org.neo4j.driver.internal.InternalRoutingControl; /** * Defines routing mode for query. * @since 5.5 */ -@Preview(name = "Driver Level Queries") -public enum RoutingControl { +public sealed interface RoutingControl extends Serializable permits InternalRoutingControl { /** * Routes to the leader of the cluster. + * @since 5.8 */ - WRITERS, + RoutingControl WRITE = InternalRoutingControl.WRITE; /** * Routes to the followers in the cluster. + * @since 5.8 */ - READERS + RoutingControl READ = InternalRoutingControl.READ; } diff --git a/driver/src/main/java/org/neo4j/driver/internal/InternalExecutableQuery.java b/driver/src/main/java/org/neo4j/driver/internal/InternalExecutableQuery.java index 85e918d576..160e93f1f0 100644 --- a/driver/src/main/java/org/neo4j/driver/internal/InternalExecutableQuery.java +++ b/driver/src/main/java/org/neo4j/driver/internal/InternalExecutableQuery.java @@ -27,6 +27,7 @@ import org.neo4j.driver.Query; import org.neo4j.driver.QueryConfig; import org.neo4j.driver.Record; +import org.neo4j.driver.RoutingControl; import org.neo4j.driver.SessionConfig; import org.neo4j.driver.TransactionCallback; @@ -77,10 +78,9 @@ public T execute(Collector recordCollector, ResultFinish var summary = result.consume(); return resultFinisher.finish(result.keys(), finishedValue, summary); }; - return switch (config.routing()) { - case WRITERS -> session.executeWrite(txCallback); - case READERS -> session.executeRead(txCallback); - }; + return config.routing().equals(RoutingControl.READ) + ? session.executeRead(txCallback) + : session.executeWrite(txCallback); } } diff --git a/driver/src/main/java/org/neo4j/driver/internal/InternalRoutingControl.java b/driver/src/main/java/org/neo4j/driver/internal/InternalRoutingControl.java new file mode 100644 index 0000000000..b197893c02 --- /dev/null +++ b/driver/src/main/java/org/neo4j/driver/internal/InternalRoutingControl.java @@ -0,0 +1,51 @@ +/* + * Copyright (c) "Neo4j" + * Neo4j Sweden AB [http://neo4j.com] + * + * This file is part of Neo4j. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.neo4j.driver.internal; + +import java.io.Serial; +import java.util.Objects; +import org.neo4j.driver.RoutingControl; + +public final class InternalRoutingControl implements RoutingControl { + public static final RoutingControl WRITE = new InternalRoutingControl("WRITE"); + public static final RoutingControl READ = new InternalRoutingControl("READ"); + + @Serial + private static final long serialVersionUID = 6766432177358809940L; + + private final String mode; + + private InternalRoutingControl(String mode) { + Objects.requireNonNull(mode, "mode must not be null"); + this.mode = mode; + } + + @Override + public boolean equals(Object o) { + if (this == o) return true; + if (o == null || getClass() != o.getClass()) return false; + InternalRoutingControl that = (InternalRoutingControl) o; + return mode.equals(that.mode); + } + + @Override + public int hashCode() { + return Objects.hash(mode); + } +} diff --git a/driver/src/test/java/org/neo4j/driver/QueryConfigTest.java b/driver/src/test/java/org/neo4j/driver/QueryConfigTest.java index ab4d23a40e..e47d3f8ea1 100644 --- a/driver/src/test/java/org/neo4j/driver/QueryConfigTest.java +++ b/driver/src/test/java/org/neo4j/driver/QueryConfigTest.java @@ -23,9 +23,10 @@ import static org.junit.jupiter.api.Assertions.assertTrue; import static org.mockito.Mockito.mock; +import java.util.List; import org.junit.jupiter.api.Test; import org.junit.jupiter.params.ParameterizedTest; -import org.junit.jupiter.params.provider.EnumSource; +import org.junit.jupiter.params.provider.MethodSource; import org.mockito.Mockito; import org.neo4j.driver.summary.ResultSummary; import org.neo4j.driver.testutil.TestUtil; @@ -36,14 +37,18 @@ void shouldReturnDefaultValues() { var config = QueryConfig.defaultConfig(); var manager = Mockito.mock(BookmarkManager.class); - assertEquals(RoutingControl.WRITERS, config.routing()); + assertEquals(RoutingControl.WRITE, config.routing()); assertTrue(config.database().isEmpty()); assertTrue(config.impersonatedUser().isEmpty()); assertEquals(manager, config.bookmarkManager(manager).get()); } + static List routingControls() { + return List.of(RoutingControl.READ, RoutingControl.WRITE); + } + @ParameterizedTest - @EnumSource(RoutingControl.class) + @MethodSource("routingControls") void shouldUpdateRouting(RoutingControl routing) { var config = QueryConfig.builder().withRouting(routing).build(); assertEquals(routing, config.routing()); diff --git a/driver/src/test/java/org/neo4j/driver/internal/InternalExecutableQueryTest.java b/driver/src/test/java/org/neo4j/driver/internal/InternalExecutableQueryTest.java index 2c1fc78c30..2367b5627b 100644 --- a/driver/src/test/java/org/neo4j/driver/internal/InternalExecutableQueryTest.java +++ b/driver/src/test/java/org/neo4j/driver/internal/InternalExecutableQueryTest.java @@ -34,7 +34,7 @@ import java.util.stream.Collector; import org.junit.jupiter.api.Test; import org.junit.jupiter.params.ParameterizedTest; -import org.junit.jupiter.params.provider.EnumSource; +import org.junit.jupiter.params.provider.MethodSource; import org.mockito.ArgumentCaptor; import org.neo4j.driver.BookmarkManager; import org.neo4j.driver.Driver; @@ -114,8 +114,12 @@ void shouldUpdateConfig() { assertEquals(config, executableQuery.config()); } + static List routingControls() { + return List.of(RoutingControl.READ, RoutingControl.WRITE); + } + @ParameterizedTest - @EnumSource(RoutingControl.class) + @MethodSource("routingControls") @SuppressWarnings("unchecked") void shouldExecuteAndReturnResult(RoutingControl routingControl) { // GIVEN @@ -126,10 +130,7 @@ void shouldExecuteAndReturnResult(RoutingControl routingControl) { given(driver.session(any(SessionConfig.class))).willReturn(session); var txContext = mock(TransactionContext.class); BiFunction, Object> executeMethod = - switch (routingControl) { - case WRITERS -> Session::executeWrite; - case READERS -> Session::executeRead; - }; + routingControl.equals(RoutingControl.READ) ? Session::executeRead : Session::executeWrite; given(executeMethod.apply(session, any())).willAnswer(answer -> { TransactionCallback txCallback = answer.getArgument(0); return txCallback.execute(txContext); diff --git a/testkit-backend/src/main/java/neo4j/org/testkit/backend/messages/requests/ExecuteQuery.java b/testkit-backend/src/main/java/neo4j/org/testkit/backend/messages/requests/ExecuteQuery.java index bfc75a7931..035d0fc21a 100644 --- a/testkit-backend/src/main/java/neo4j/org/testkit/backend/messages/requests/ExecuteQuery.java +++ b/testkit-backend/src/main/java/neo4j/org/testkit/backend/messages/requests/ExecuteQuery.java @@ -45,8 +45,8 @@ public TestkitResponse process(TestkitState testkitState) { var routing = data.getConfig().getRouting(); if (data.getConfig().getRouting() != null) { switch (routing) { - case "w" -> configBuilder.withRouting(RoutingControl.WRITERS); - case "r" -> configBuilder.withRouting(RoutingControl.READERS); + case "w" -> configBuilder.withRouting(RoutingControl.WRITE); + case "r" -> configBuilder.withRouting(RoutingControl.READ); default -> throw new IllegalArgumentException(); } }