Skip to content

Make Driver Level Queries API GA #1407

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 2 commits into from
Apr 12, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
7 changes: 3 additions & 4 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -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());
}
```

Expand Down
35 changes: 35 additions & 0 deletions driver/clirr-ignored-differences.xml
Original file line number Diff line number Diff line change
Expand Up @@ -503,4 +503,39 @@
<method>org.neo4j.driver.BookmarkManager executableQueryBookmarkManager()</method>
</difference>

<difference>
<className>org/neo4j/driver/RoutingControl</className>
<differenceType>6001</differenceType>
<field>WRITERS</field>
</difference>

<difference>
<className>org/neo4j/driver/RoutingControl</className>
<differenceType>6001</differenceType>
<field>READERS</field>
</difference>

<difference>
<className>org/neo4j/driver/RoutingControl</className>
<differenceType>2000</differenceType>
</difference>

<difference>
<className>org/neo4j/driver/RoutingControl</className>
<differenceType>4001</differenceType>
<to>java/lang/Comparable</to>
</difference>

<difference>
<className>org/neo4j/driver/RoutingControl</className>
<differenceType>4001</differenceType>
<to>java/lang/constant/Constable</to>
</difference>

<difference>
<className>org/neo4j/driver/RoutingControl</className>
<differenceType>5001</differenceType>
<to>java/lang/Enum</to>
</difference>

</differences>
3 changes: 0 additions & 3 deletions driver/src/main/java/org/neo4j/driver/Driver.java
Original file line number Diff line number Diff line change
Expand Up @@ -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.
Expand Down Expand Up @@ -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);

/**
Expand All @@ -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();

/**
Expand Down
2 changes: 0 additions & 2 deletions driver/src/main/java/org/neo4j/driver/EagerResult.java
Original file line number Diff line number Diff line change
Expand Up @@ -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.
Expand Down
3 changes: 0 additions & 3 deletions driver/src/main/java/org/neo4j/driver/ExecutableQuery.java
Original file line number Diff line number Diff line change
Expand Up @@ -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.
Expand Down Expand Up @@ -96,7 +95,6 @@
*
* @since 5.7
*/
@Preview(name = "Driver Level Queries")
public interface ExecutableQuery {
/**
* Sets query parameters.
Expand Down Expand Up @@ -166,7 +164,6 @@ default <T> T execute(Collector<Record, ?, T> recordCollector) {
* @param <T> the final value type
* @since 5.5
*/
@Preview(name = "Driver Level Queries")
@FunctionalInterface
interface ResultFinisher<S, T> {
/**
Expand Down
4 changes: 1 addition & 3 deletions driver/src/main/java/org/neo4j/driver/QueryConfig.java
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down Expand Up @@ -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;
Expand Down
12 changes: 7 additions & 5 deletions driver/src/main/java/org/neo4j/driver/RoutingControl.java
Original file line number Diff line number Diff line change
Expand Up @@ -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;
}
Original file line number Diff line number Diff line change
Expand Up @@ -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;

Expand Down Expand Up @@ -77,10 +78,9 @@ public <A, R, T> T execute(Collector<Record, A, R> 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);
}
}

Expand Down
Original file line number Diff line number Diff line change
@@ -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);
}
}
11 changes: 8 additions & 3 deletions driver/src/test/java/org/neo4j/driver/QueryConfigTest.java
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand All @@ -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<RoutingControl> 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());
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down Expand Up @@ -114,8 +114,12 @@ void shouldUpdateConfig() {
assertEquals(config, executableQuery.config());
}

static List<RoutingControl> routingControls() {
return List.of(RoutingControl.READ, RoutingControl.WRITE);
}

@ParameterizedTest
@EnumSource(RoutingControl.class)
@MethodSource("routingControls")
@SuppressWarnings("unchecked")
void shouldExecuteAndReturnResult(RoutingControl routingControl) {
// GIVEN
Expand All @@ -126,10 +130,7 @@ void shouldExecuteAndReturnResult(RoutingControl routingControl) {
given(driver.session(any(SessionConfig.class))).willReturn(session);
var txContext = mock(TransactionContext.class);
BiFunction<Session, TransactionCallback<Object>, 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);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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();
}
}
Expand Down