Skip to content

Commit 8115a01

Browse files
authored
Add a new QueryTask.ResultFinisher interface (#1367)
This is to make the `Result.keys()` value available as well.
1 parent 361e779 commit 8115a01

File tree

3 files changed

+50
-30
lines changed

3 files changed

+50
-30
lines changed

driver/src/main/java/org/neo4j/driver/QueryTask.java

Lines changed: 41 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -18,10 +18,13 @@
1818
*/
1919
package org.neo4j.driver;
2020

21+
import java.util.List;
2122
import java.util.Map;
2223
import java.util.function.BiFunction;
2324
import java.util.function.Consumer;
2425
import java.util.stream.Collector;
26+
import java.util.stream.Collectors;
27+
import org.neo4j.driver.internal.EagerResultValue;
2528
import org.neo4j.driver.summary.ResultSummary;
2629
import org.neo4j.driver.util.Experimental;
2730

@@ -79,12 +82,13 @@
7982
* .execute(mapping(record -> record.get("N").asLong(), maxBy(Long::compare)));
8083
* }
8184
* </pre>
82-
* If there is a need to access {@link ResultSummary} value, another method option is available:
85+
* If there is a need to access {@link Result#keys()} and/or {@link ResultSummary} value, another method option is
86+
* available:
8387
* <pre>
8488
* {@code
8589
* import static java.util.stream.Collectors.*;
8690
*
87-
* private record ResultValue(Set<Long> values, ResultSummary summary) {}
91+
* private record ResultValue(List<String> keys, Set<Long> values, ResultSummary summary) {}
8892
*
8993
* var result = driver.queryTask("UNWIND range(0, 5) as N RETURN N")
9094
* .execute(Collectors.mapping(record -> record.get("N").asLong(), toSet()), ResultValue::new);
@@ -118,7 +122,9 @@ public interface QueryTask {
118122
*
119123
* @return an instance of result containing all records, keys and result summary
120124
*/
121-
EagerResult execute();
125+
default EagerResult execute() {
126+
return execute(Collectors.toList(), EagerResultValue::new);
127+
}
122128

123129
/**
124130
* Executes query, collects {@link Record} values using the provided {@link Collector} and produces a final result.
@@ -129,22 +135,49 @@ public interface QueryTask {
129135
* @return the final result value
130136
*/
131137
default <T> T execute(Collector<Record, ?, T> recordCollector) {
132-
return execute(recordCollector, (collectorResult, ignored) -> collectorResult);
138+
return execute(recordCollector, (ignoredKeys, collectorResult, ignoredSummary) -> collectorResult);
133139
}
134140

135141
/**
136142
* Executes query, collects {@link Record} values using the provided {@link Collector} and produces a final result
137143
* by invoking the provided {@link BiFunction} with the collected result and {@link ResultSummary} values.
144+
* <p>
145+
* If any of the arguments throws an exception implementing the
146+
* {@link org.neo4j.driver.exceptions.RetryableException} marker interface, the query is retried automatically in
147+
* the same way as in the transaction functions. Exceptions not implementing the interface trigger transaction
148+
* rollback and are then propagated to the user.
138149
*
139150
* @param recordCollector collector instance responsible for processing {@link Record} values and producing a
140151
* collected result, the collector may be used multiple times if query is retried
141-
* @param finisherWithSummary function accepting both the collected result and {@link ResultSummary} values to
142-
* output the final result, the function may be invoked multiple times if query is
143-
* retried
152+
* @param resultFinisher function accepting the {@link Result#keys()}, collected result and {@link ResultSummary}
153+
* values to output the final result value, the function may be invoked multiple times if
154+
* query is retried
144155
* @param <A> the mutable accumulation type of the collector's reduction operation
145156
* @param <R> the collector's result type
146157
* @param <T> the final result type
147158
* @return the final result value
148159
*/
149-
<A, R, T> T execute(Collector<Record, A, R> recordCollector, BiFunction<R, ResultSummary, T> finisherWithSummary);
160+
<A, R, T> T execute(Collector<Record, A, R> recordCollector, ResultFinisher<R, T> resultFinisher);
161+
162+
/**
163+
* A function accepting the {@link Result#keys()}, collected result and {@link ResultSummary} values to produce a
164+
* final result value.
165+
*
166+
* @param <S> the collected value type
167+
* @param <T> the final value type
168+
* @since 5.5
169+
*/
170+
@Experimental
171+
@FunctionalInterface
172+
interface ResultFinisher<S, T> {
173+
/**
174+
* Accepts the {@link Result#keys()}, collected result and {@link ResultSummary} values to produce the final
175+
* result value.
176+
* @param value the collected value
177+
* @param keys the {@link Result#keys()} value
178+
* @param summary the {@link ResultSummary} value
179+
* @return the final value
180+
*/
181+
T finish(List<String> keys, S value, ResultSummary summary);
182+
}
150183
}

driver/src/main/java/org/neo4j/driver/internal/InternalQueryTask.java

Lines changed: 2 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -20,28 +20,17 @@
2020

2121
import static java.util.Objects.requireNonNull;
2222

23-
import java.util.Collections;
24-
import java.util.List;
2523
import java.util.Map;
26-
import java.util.function.BiFunction;
2724
import java.util.stream.Collector;
28-
import java.util.stream.Collectors;
2925
import org.neo4j.driver.Driver;
30-
import org.neo4j.driver.EagerResult;
3126
import org.neo4j.driver.Query;
3227
import org.neo4j.driver.QueryConfig;
3328
import org.neo4j.driver.QueryTask;
3429
import org.neo4j.driver.Record;
3530
import org.neo4j.driver.SessionConfig;
3631
import org.neo4j.driver.TransactionCallback;
37-
import org.neo4j.driver.summary.ResultSummary;
3832

3933
public class InternalQueryTask implements QueryTask {
40-
private static final BiFunction<List<Record>, ResultSummary, EagerResult> EAGER_RESULT_FINISHER =
41-
(records, summary) -> {
42-
var keys = records.stream().findFirst().map(Record::keys).orElseGet(Collections::emptyList);
43-
return new EagerResultValue(keys, records, summary);
44-
};
4534
private final Driver driver;
4635
private final Query query;
4736
private final QueryConfig config;
@@ -68,13 +57,7 @@ public QueryTask withConfig(QueryConfig config) {
6857
}
6958

7059
@Override
71-
public EagerResult execute() {
72-
return execute(Collectors.toList(), EAGER_RESULT_FINISHER);
73-
}
74-
75-
@Override
76-
public <A, R, T> T execute(
77-
Collector<Record, A, R> recordCollector, BiFunction<R, ResultSummary, T> finisherWithSummary) {
60+
public <A, R, T> T execute(Collector<Record, A, R> recordCollector, ResultFinisher<R, T> resultFinisher) {
7861
var sessionConfigBuilder = SessionConfig.builder();
7962
config.database().ifPresent(sessionConfigBuilder::withDatabase);
8063
config.impersonatedUser().ifPresent(sessionConfigBuilder::withImpersonatedUser);
@@ -91,7 +74,7 @@ public <A, R, T> T execute(
9174
}
9275
var finishedValue = finisher.apply(container);
9376
var summary = result.consume();
94-
return finisherWithSummary.apply(finishedValue, summary);
77+
return resultFinisher.finish(result.keys(), finishedValue, summary);
9578
};
9679
return switch (config.routing()) {
9780
case WRITERS -> session.executeWrite(txCallback);

driver/src/test/java/org/neo4j/driver/internal/InternalQueryTaskTest.java

Lines changed: 7 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -26,6 +26,7 @@
2626
import static org.mockito.Mockito.mock;
2727
import static org.mockito.Mockito.times;
2828

29+
import java.util.List;
2930
import java.util.Map;
3031
import java.util.function.BiConsumer;
3132
import java.util.function.BiFunction;
@@ -39,6 +40,7 @@
3940
import org.neo4j.driver.Driver;
4041
import org.neo4j.driver.Query;
4142
import org.neo4j.driver.QueryConfig;
43+
import org.neo4j.driver.QueryTask;
4244
import org.neo4j.driver.Record;
4345
import org.neo4j.driver.Result;
4446
import org.neo4j.driver.RoutingControl;
@@ -131,6 +133,8 @@ void shouldExecuteAndReturnResult(RoutingControl routingControl) {
131133
});
132134
var result = mock(Result.class);
133135
given(txContext.run(any(Query.class))).willReturn(result);
136+
var keys = List.of("key");
137+
given(result.keys()).willReturn(keys);
134138
given(result.hasNext()).willReturn(true, false);
135139
var record = mock(Record.class);
136140
given(result.next()).willReturn(record);
@@ -152,9 +156,9 @@ var record = mock(Record.class);
152156
Function<Object, String> finisher = mock(Function.class);
153157
given(finisher.apply(resultContainer)).willReturn(collectorResult);
154158
given(recordCollector.finisher()).willReturn(finisher);
155-
BiFunction<String, ResultSummary, String> finisherWithSummary = mock(BiFunction.class);
159+
QueryTask.ResultFinisher<String, String> finisherWithSummary = mock(QueryTask.ResultFinisher.class);
156160
var expectedExecuteResult = "1";
157-
given(finisherWithSummary.apply(any(String.class), any(ResultSummary.class)))
161+
given(finisherWithSummary.finish(any(List.class), any(String.class), any(ResultSummary.class)))
158162
.willReturn(expectedExecuteResult);
159163
var queryTask = new InternalQueryTask(driver, query, config).withParameters(params);
160164

@@ -181,7 +185,7 @@ var record = mock(Record.class);
181185
then(accumulator).should().accept(resultContainer, record);
182186
then(recordCollector).should().finisher();
183187
then(finisher).should().apply(resultContainer);
184-
then(finisherWithSummary).should().apply(collectorResult, summary);
188+
then(finisherWithSummary).should().finish(keys, collectorResult, summary);
185189
assertEquals(expectedExecuteResult, executeResult);
186190
}
187191
}

0 commit comments

Comments
 (0)