Skip to content

Commit ff1f4fd

Browse files
committed
Update ResultTransformer
1 parent fd59843 commit ff1f4fd

File tree

11 files changed

+213
-86
lines changed

11 files changed

+213
-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: 21 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -35,10 +35,10 @@ public final class QueryConfig<T> implements Serializable {
3535
@Serial
3636
private static final long serialVersionUID = -2632780731598141754L;
3737

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

4040
private final RoutingControl routing;
41-
private final ResultTransformer<T> resultTransformer;
41+
private final SerializableSupplier<ResultTransformer<T>> resultTransformerSupplier;
4242
private final String database;
4343
private final String impersonatedUser;
4444
private final BookmarkManager bookmarkManager;
@@ -50,12 +50,12 @@ public final class QueryConfig<T> implements Serializable {
5050
* @return config value
5151
*/
5252
public static QueryConfig<EagerResult> defaultConfig() {
53-
return DEFAULT_VALUE;
53+
return DEFAULT;
5454
}
5555

5656
private QueryConfig(Builder<T> builder) {
5757
this.routing = builder.routing;
58-
this.resultTransformer = builder.resultTransformer;
58+
this.resultTransformerSupplier = builder.resultTransformerSupplier;
5959
this.database = builder.database;
6060
this.impersonatedUser = builder.impersonatedUser;
6161
this.bookmarkManager = builder.bookmarkManager;
@@ -69,7 +69,7 @@ private QueryConfig(Builder<T> builder) {
6969
* @return a query configuration builder
7070
*/
7171
public static Builder<EagerResult> builder() {
72-
return new Builder<>(new EagerResultTransformer());
72+
return new Builder<>(EagerResultTransformer::new);
7373
}
7474

7575
/**
@@ -78,9 +78,9 @@ public static Builder<EagerResult> builder() {
7878
*
7979
* @return a query configuration builder
8080
*/
81-
public static <T> Builder<T> builder(ResultTransformer<T> resultTransformer) {
82-
requireNonNull(resultTransformer, "resultTransformer must not be null");
83-
return new Builder<>(resultTransformer);
81+
public static <T> Builder<T> builder(SerializableSupplier<ResultTransformer<T>> resultTransformerSupplier) {
82+
requireNonNull(resultTransformerSupplier, "resultTransformerSupplier must not be null");
83+
return new Builder<>(resultTransformerSupplier);
8484
}
8585

8686
/**
@@ -97,8 +97,8 @@ public RoutingControl routing() {
9797
*
9898
* @return result mapper
9999
*/
100-
public ResultTransformer<T> resultTransformer() {
101-
return resultTransformer;
100+
public SerializableSupplier<ResultTransformer<T>> resultTransformerSupplier() {
101+
return resultTransformerSupplier;
102102
}
103103

104104
/**
@@ -138,7 +138,7 @@ public boolean equals(Object o) {
138138
QueryConfig<?> that = (QueryConfig<?>) o;
139139
return useDefaultBookmarkManager == that.useDefaultBookmarkManager
140140
&& routing == that.routing
141-
&& resultTransformer.equals(that.resultTransformer)
141+
&& resultTransformerSupplier.equals(that.resultTransformerSupplier)
142142
&& Objects.equals(database, that.database)
143143
&& Objects.equals(impersonatedUser, that.impersonatedUser)
144144
&& Objects.equals(bookmarkManager, that.bookmarkManager);
@@ -147,14 +147,19 @@ public boolean equals(Object o) {
147147
@Override
148148
public int hashCode() {
149149
return Objects.hash(
150-
routing, resultTransformer, database, impersonatedUser, bookmarkManager, useDefaultBookmarkManager);
150+
routing,
151+
resultTransformerSupplier,
152+
database,
153+
impersonatedUser,
154+
bookmarkManager,
155+
useDefaultBookmarkManager);
151156
}
152157

153158
@Override
154159
public String toString() {
155160
return "QueryConfig{" + "routing="
156161
+ routing + ", resultTransformer="
157-
+ resultTransformer + ", database='"
162+
+ resultTransformerSupplier + ", database='"
158163
+ database + '\'' + ", impersonatedUser='"
159164
+ impersonatedUser + '\'' + ", bookmarkManager="
160165
+ bookmarkManager + ", useDefaultBookmarkManager="
@@ -166,14 +171,14 @@ public String toString() {
166171
*/
167172
public static final class Builder<T> {
168173
private RoutingControl routing = RoutingControl.WRITERS;
169-
private final ResultTransformer<T> resultTransformer;
174+
private final SerializableSupplier<ResultTransformer<T>> resultTransformerSupplier;
170175
private String database;
171176
private String impersonatedUser;
172177
private BookmarkManager bookmarkManager;
173178
private boolean useDefaultBookmarkManager = true;
174179

175-
private Builder(ResultTransformer<T> resultTransformer) {
176-
this.resultTransformer = resultTransformer;
180+
private Builder(SerializableSupplier<ResultTransformer<T>> resultTransformerSupplier) {
181+
this.resultTransformerSupplier = resultTransformerSupplier;
177182
}
178183

179184
/**

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 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+
* If return value is {@code 0}, the driver skips further records and invokes the {@link #finish(ResultSummary)} method to finish the transformation.
40+
* <p>
41+
* The default value is {@link Long#MAX_VALUE}, meaning all records would be supplied.
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+
}
Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,24 @@
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.io.Serializable;
22+
import java.util.function.Supplier;
23+
24+
public interface SerializableSupplier<T> extends Supplier<T>, Serializable {}

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+
}

0 commit comments

Comments
 (0)