Skip to content

Commit 24756ef

Browse files
committed
Update ResultTransformer
1 parent fd59843 commit 24756ef

File tree

10 files changed

+191
-86
lines changed

10 files changed

+191
-86
lines changed

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

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -119,8 +119,8 @@ default EagerResult executeQuery(Query query) {
119119
*
120120
* @param query a query value to execute
121121
* @param config a query execution config value
122-
* @param <T> a type managed by {@link QueryConfig#resultTransformer()}
123-
* @return a query execution result value of a type managed by {@link QueryConfig#resultTransformer()}
122+
* @param <T> a type managed by {@link QueryConfig#resultTransformerSupplier()}
123+
* @return a query execution result value of a type managed by {@link QueryConfig#resultTransformerSupplier()}
124124
*/
125125
<T> T executeQuery(Query query, QueryConfig<T> config);
126126

@@ -160,8 +160,8 @@ default EagerResult executeQuery(String query, Map<String, Object> parameters) {
160160
* @param query a query string to execute
161161
* @param parameters a query parameters
162162
* @param config a query execution config value
163-
* @param <T> a type managed by {@link QueryConfig#resultTransformer()}
164-
* @return a query execution result value of a type managed by {@link QueryConfig#resultTransformer()}
163+
* @param <T> a type managed by {@link QueryConfig#resultTransformerSupplier()}
164+
* @return a query execution result value of a type managed by {@link QueryConfig#resultTransformerSupplier()}
165165
*/
166166
default <T> T executeQuery(String query, Map<String, Object> parameters, QueryConfig<T> config) {
167167
return executeQuery(new Query(query, parameters), config);

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

Lines changed: 22 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -24,6 +24,7 @@
2424
import java.io.Serializable;
2525
import java.util.Objects;
2626
import java.util.Optional;
27+
import java.util.function.Supplier;
2728
import org.neo4j.driver.internal.EagerResultTransformer;
2829

2930
/**
@@ -35,10 +36,10 @@ public final class QueryConfig<T> implements Serializable {
3536
@Serial
3637
private static final long serialVersionUID = -2632780731598141754L;
3738

38-
private static final QueryConfig<EagerResult> DEFAULT_VALUE = builder().build();
39+
private static final QueryConfig<EagerResult> DEFAULT = builder().build();
3940

4041
private final RoutingControl routing;
41-
private final ResultTransformer<T> resultTransformer;
42+
private final Supplier<ResultTransformer<T>> resultTransformerSupplier;
4243
private final String database;
4344
private final String impersonatedUser;
4445
private final BookmarkManager bookmarkManager;
@@ -50,12 +51,12 @@ public final class QueryConfig<T> implements Serializable {
5051
* @return config value
5152
*/
5253
public static QueryConfig<EagerResult> defaultConfig() {
53-
return DEFAULT_VALUE;
54+
return DEFAULT;
5455
}
5556

5657
private QueryConfig(Builder<T> builder) {
5758
this.routing = builder.routing;
58-
this.resultTransformer = builder.resultTransformer;
59+
this.resultTransformerSupplier = builder.resultTransformerSupplier;
5960
this.database = builder.database;
6061
this.impersonatedUser = builder.impersonatedUser;
6162
this.bookmarkManager = builder.bookmarkManager;
@@ -69,7 +70,7 @@ private QueryConfig(Builder<T> builder) {
6970
* @return a query configuration builder
7071
*/
7172
public static Builder<EagerResult> builder() {
72-
return new Builder<>(new EagerResultTransformer());
73+
return new Builder<>(EagerResultTransformer::new);
7374
}
7475

7576
/**
@@ -78,9 +79,9 @@ public static Builder<EagerResult> builder() {
7879
*
7980
* @return a query configuration builder
8081
*/
81-
public static <T> Builder<T> builder(ResultTransformer<T> resultTransformer) {
82-
requireNonNull(resultTransformer, "resultTransformer must not be null");
83-
return new Builder<>(resultTransformer);
82+
public static <T> Builder<T> builder(Supplier<ResultTransformer<T>> resultTransformerSupplier) {
83+
requireNonNull(resultTransformerSupplier, "resultTransformerSupplier must not be null");
84+
return new Builder<>(resultTransformerSupplier);
8485
}
8586

8687
/**
@@ -97,8 +98,8 @@ public RoutingControl routing() {
9798
*
9899
* @return result mapper
99100
*/
100-
public ResultTransformer<T> resultTransformer() {
101-
return resultTransformer;
101+
public Supplier<ResultTransformer<T>> resultTransformerSupplier() {
102+
return resultTransformerSupplier;
102103
}
103104

104105
/**
@@ -138,7 +139,7 @@ public boolean equals(Object o) {
138139
QueryConfig<?> that = (QueryConfig<?>) o;
139140
return useDefaultBookmarkManager == that.useDefaultBookmarkManager
140141
&& routing == that.routing
141-
&& resultTransformer.equals(that.resultTransformer)
142+
&& resultTransformerSupplier.equals(that.resultTransformerSupplier)
142143
&& Objects.equals(database, that.database)
143144
&& Objects.equals(impersonatedUser, that.impersonatedUser)
144145
&& Objects.equals(bookmarkManager, that.bookmarkManager);
@@ -147,14 +148,19 @@ public boolean equals(Object o) {
147148
@Override
148149
public int hashCode() {
149150
return Objects.hash(
150-
routing, resultTransformer, database, impersonatedUser, bookmarkManager, useDefaultBookmarkManager);
151+
routing,
152+
resultTransformerSupplier,
153+
database,
154+
impersonatedUser,
155+
bookmarkManager,
156+
useDefaultBookmarkManager);
151157
}
152158

153159
@Override
154160
public String toString() {
155161
return "QueryConfig{" + "routing="
156162
+ routing + ", resultTransformer="
157-
+ resultTransformer + ", database='"
163+
+ resultTransformerSupplier + ", database='"
158164
+ database + '\'' + ", impersonatedUser='"
159165
+ impersonatedUser + '\'' + ", bookmarkManager="
160166
+ bookmarkManager + ", useDefaultBookmarkManager="
@@ -166,14 +172,14 @@ public String toString() {
166172
*/
167173
public static final class Builder<T> {
168174
private RoutingControl routing = RoutingControl.WRITERS;
169-
private final ResultTransformer<T> resultTransformer;
175+
private final Supplier<ResultTransformer<T>> resultTransformerSupplier;
170176
private String database;
171177
private String impersonatedUser;
172178
private BookmarkManager bookmarkManager;
173179
private boolean useDefaultBookmarkManager = true;
174180

175-
private Builder(ResultTransformer<T> resultTransformer) {
176-
this.resultTransformer = resultTransformer;
181+
private Builder(Supplier<ResultTransformer<T>> resultTransformerSupplier) {
182+
this.resultTransformerSupplier = resultTransformerSupplier;
177183
}
178184

179185
/**

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

Lines changed: 38 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -19,21 +19,51 @@
1919
package org.neo4j.driver;
2020

2121
import java.io.Serializable;
22+
import org.neo4j.driver.summary.ResultSummary;
2223

2324
/**
24-
* A transformer responsible for handling {@link Result} and outputting result of desired type.
25+
* A transformer responsible for handling {@link java.lang.Record} values followed by a {@link ResultSummary} value and producing a final result of the target type.
2526
*
26-
* @param <T> output type
27+
* @param <T> the target type of the final result
2728
*/
28-
@FunctionalInterface
2929
public interface ResultTransformer<T> extends Serializable {
3030
/**
31-
* Transforms {@link Result} to desired output type.
31+
* Returns a {@link Record} demand number that indicates the amount of values that may be supplied via the {@link #accept(Record)} method.
3232
* <p>
33-
* This method must never return or proxy calls to the {@link Result} object as it is not guaranteed to be valid after this method supplies the output.
33+
* The driver invokes this method in the following cases:
34+
* <ol>
35+
* <li>Before supplying any values.</li>
36+
* <li>After the previous demand has been satisfied.</li>
37+
* </ol>
38+
* <p>
39+
* The default value is {@link Long#MAX_VALUE}, meaning all {@link Record} values would be supplied.
40+
* <p>
41+
* If return value is {@code 0}, the driver skips further records and invokes the {@link #finish(ResultSummary)} method to finish the transformation.
42+
*
43+
* @return the record demand
44+
*/
45+
default long recordDemand() {
46+
return Long.MAX_VALUE;
47+
}
48+
49+
/**
50+
* Accepts a {@link Record} value for transformation.
51+
*
52+
* @param record the record value
53+
*/
54+
void accept(Record record);
55+
56+
/**
57+
* Accepts a {@link ResultSummary} value and finishes the transformation by producing a final value of the target type.
58+
* <p>
59+
* This method is invoked by the driver after one of the following conditions if met:
60+
* <ol>
61+
* <li>There is no record demand, indicated by the {@link #recordDemand()} method returning {@code 0}.</li>
62+
* <li>There are no more records to supply.</li>
63+
* </ol>
3464
*
35-
* @param result result value
36-
* @return output value
65+
* @param summary the result summary
66+
* @return the final value of the target type
3767
*/
38-
T transform(Result result);
68+
T finish(ResultSummary summary);
3969
}
Lines changed: 33 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,33 @@
1+
/*
2+
* Copyright (c) "Neo4j"
3+
* Neo4j Sweden AB [http://neo4j.com]
4+
*
5+
* This file is part of Neo4j.
6+
*
7+
* Licensed under the Apache License, Version 2.0 (the "License");
8+
* you may not use this file except in compliance with the License.
9+
* You may obtain a copy of the License at
10+
*
11+
* http://www.apache.org/licenses/LICENSE-2.0
12+
*
13+
* Unless required by applicable law or agreed to in writing, software
14+
* distributed under the License is distributed on an "AS IS" BASIS,
15+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
16+
* See the License for the specific language governing permissions and
17+
* limitations under the License.
18+
*/
19+
package org.neo4j.driver;
20+
21+
import java.util.Collection;
22+
import java.util.function.Function;
23+
import java.util.function.Supplier;
24+
import org.neo4j.driver.internal.MappingCollectionResultTransformer;
25+
26+
public class ResultTransformers {
27+
private ResultTransformers() {}
28+
29+
public static <E, T extends Collection<E>> ResultTransformer<T> mappingCollection(
30+
Supplier<T> supplier, Function<Record, E> mapper) {
31+
return new MappingCollectionResultTransformer<>(supplier, mapper);
32+
}
33+
}

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

Lines changed: 18 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -19,16 +19,31 @@
1919
package org.neo4j.driver.internal;
2020

2121
import java.io.Serial;
22+
import java.util.ArrayList;
23+
import java.util.List;
2224
import org.neo4j.driver.EagerResult;
23-
import org.neo4j.driver.Result;
25+
import org.neo4j.driver.Record;
2426
import org.neo4j.driver.ResultTransformer;
27+
import org.neo4j.driver.summary.ResultSummary;
2528

2629
public final class EagerResultTransformer implements ResultTransformer<EagerResult> {
2730
@Serial
2831
private static final long serialVersionUID = 67912015934658230L;
2932

33+
private final List<String> keys = new ArrayList<>();
34+
private final List<Record> records = new ArrayList<>();
35+
36+
@Override
37+
public void accept(Record record) {
38+
var recordKeys = record.keys();
39+
if (keys.isEmpty() && !recordKeys.isEmpty()) {
40+
keys.addAll(recordKeys);
41+
}
42+
records.add(record);
43+
}
44+
3045
@Override
31-
public EagerResult transform(Result result) {
32-
return new EagerResultValue(result.keys(), result.list(), result.consume());
46+
public EagerResult finish(ResultSummary summary) {
47+
return new EagerResultValue(keys, records, summary);
3348
}
3449
}

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

Lines changed: 8 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -31,7 +31,6 @@
3131
import org.neo4j.driver.Metrics;
3232
import org.neo4j.driver.Query;
3333
import org.neo4j.driver.QueryConfig;
34-
import org.neo4j.driver.Result;
3534
import org.neo4j.driver.Session;
3635
import org.neo4j.driver.SessionConfig;
3736
import org.neo4j.driver.TransactionCallback;
@@ -78,13 +77,15 @@ public <T> T executeQuery(Query query, QueryConfig<T> config) {
7877
try (var session = session(sessionConfigBuilder.build())) {
7978
TransactionCallback<T> txCallback = tx -> {
8079
var result = tx.run(query);
81-
var transformedResult = config.resultTransformer().transform(result);
82-
if (transformedResult instanceof Result) {
83-
throw new IllegalStateException(String.format(
84-
"Illegal result returned by %s, it must never be an instance of %s",
85-
config.resultTransformer().getClass().getName(), Result.class.getName()));
80+
var transformer = config.resultTransformerSupplier().get();
81+
var remainingDemand = transformer.recordDemand();
82+
while (remainingDemand > 0 && result.hasNext()) {
83+
transformer.accept(result.next());
84+
if (--remainingDemand == 0) {
85+
remainingDemand = transformer.recordDemand();
86+
}
8687
}
87-
return transformedResult;
88+
return transformer.finish(result.consume());
8889
};
8990
return switch (config.routing()) {
9091
case WRITERS -> session.executeWrite(txCallback);
Lines changed: 50 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,50 @@
1+
/*
2+
* Copyright (c) "Neo4j"
3+
* Neo4j Sweden AB [http://neo4j.com]
4+
*
5+
* This file is part of Neo4j.
6+
*
7+
* Licensed under the Apache License, Version 2.0 (the "License");
8+
* you may not use this file except in compliance with the License.
9+
* You may obtain a copy of the License at
10+
*
11+
* http://www.apache.org/licenses/LICENSE-2.0
12+
*
13+
* Unless required by applicable law or agreed to in writing, software
14+
* distributed under the License is distributed on an "AS IS" BASIS,
15+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
16+
* See the License for the specific language governing permissions and
17+
* limitations under the License.
18+
*/
19+
package org.neo4j.driver.internal;
20+
21+
import java.io.Serial;
22+
import java.util.Collection;
23+
import java.util.function.Function;
24+
import java.util.function.Supplier;
25+
import org.neo4j.driver.Record;
26+
import org.neo4j.driver.ResultTransformer;
27+
import org.neo4j.driver.summary.ResultSummary;
28+
29+
public class MappingCollectionResultTransformer<E, T extends Collection<E>> implements ResultTransformer<T> {
30+
@Serial
31+
private static final long serialVersionUID = 67912015934658225L;
32+
33+
private final T values;
34+
private final Function<Record, E> mapper;
35+
36+
public MappingCollectionResultTransformer(Supplier<T> supplier, Function<Record, E> mapper) {
37+
this.values = supplier.get();
38+
this.mapper = mapper;
39+
}
40+
41+
@Override
42+
public void accept(Record record) {
43+
values.add(mapper.apply(record));
44+
}
45+
46+
@Override
47+
public T finish(ResultSummary summary) {
48+
return values;
49+
}
50+
}

driver/src/test/java/org/neo4j/driver/QueryConfigTest.java

Lines changed: 10 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -23,6 +23,9 @@
2323
import static org.junit.jupiter.api.Assertions.assertTrue;
2424
import static org.mockito.Mockito.mock;
2525

26+
import java.util.ArrayList;
27+
import java.util.List;
28+
import java.util.function.Supplier;
2629
import org.junit.jupiter.api.Test;
2730
import org.junit.jupiter.params.ParameterizedTest;
2831
import org.junit.jupiter.params.provider.EnumSource;
@@ -36,7 +39,7 @@ void shouldReturnDefaultValues() {
3639
var manager = mock(BookmarkManager.class);
3740

3841
assertEquals(RoutingControl.WRITERS, config.routing());
39-
assertTrue(config.resultTransformer() instanceof EagerResultTransformer);
42+
assertTrue(config.resultTransformerSupplier().get() instanceof EagerResultTransformer);
4043
assertTrue(config.database().isEmpty());
4144
assertTrue(config.impersonatedUser().isEmpty());
4245
assertEquals(manager, config.bookmarkManager(manager).get());
@@ -56,9 +59,10 @@ void shouldNotAllowNullRouting() {
5659

5760
@Test
5861
void shouldUpdateResultTransformer() {
59-
ResultTransformer<String> transformer = ignored -> "result";
60-
var config = QueryConfig.builder(transformer).build();
61-
assertEquals(transformer, config.resultTransformer());
62+
Supplier<ResultTransformer<List<String>>> transformerSupplier =
63+
() -> ResultTransformers.mappingCollection(ArrayList::new, ignored -> "v");
64+
var config = QueryConfig.builder(transformerSupplier).build();
65+
assertEquals(transformerSupplier, config.resultTransformerSupplier());
6266
}
6367

6468
@Test
@@ -115,8 +119,8 @@ void shouldSerialize() throws Exception {
115119

116120
assertEquals(originalConfig.routing(), deserializedConfig.routing());
117121
assertEquals(
118-
originalConfig.resultTransformer().getClass(),
119-
deserializedConfig.resultTransformer().getClass());
122+
originalConfig.resultTransformerSupplier().getClass(),
123+
deserializedConfig.resultTransformerSupplier().getClass());
120124
assertEquals(originalConfig.database(), deserializedConfig.database());
121125
assertEquals(originalConfig.impersonatedUser(), deserializedConfig.impersonatedUser());
122126
assertEquals(

0 commit comments

Comments
 (0)