-
-
Notifications
You must be signed in to change notification settings - Fork 336
/
ResultIterable.java
306 lines (276 loc) · 9.87 KB
/
ResultIterable.java
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
/*
* 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.jdbi.v3.core.result;
import java.sql.ResultSet;
import java.sql.SQLException;
import java.util.List;
import java.util.Optional;
import java.util.function.BiFunction;
import java.util.function.Consumer;
import java.util.function.Function;
import java.util.function.Supplier;
import java.util.stream.Collector;
import java.util.stream.Collectors;
import java.util.stream.Stream;
import java.util.stream.StreamSupport;
import org.jdbi.v3.core.mapper.RowMapper;
import org.jdbi.v3.core.statement.StatementContext;
import static java.util.Spliterators.spliteratorUnknownSize;
/**
* An {@link Iterable} of values, usually mapped from a {@link java.sql.ResultSet}. Generally, ResultIterables may only
* be traversed once.
*
* @param <T> iterable element type
*/
@FunctionalInterface
public interface ResultIterable<T> extends Iterable<T> {
/**
* Returns a ResultIterable backed by the given result set supplier, mapper, and context.
*
* @param supplier result set supplier
* @param mapper row mapper
* @param ctx statement context
* @param <T> the mapped type
* @return the result iterable
*/
static <T> ResultIterable<T> of(Supplier<ResultSet> supplier, RowMapper<T> mapper, StatementContext ctx) {
return () -> {
try {
return new ResultSetResultIterator<>(supplier.get(), mapper, ctx);
} catch (SQLException e) {
try {
ctx.close();
} catch (Exception e1) {
e.addSuppressed(e1);
}
throw new ResultSetException("Unable to iterator result set", e, ctx);
}
};
}
/**
* Returns a ResultIterable backed by the given iterator.
* @param iterator the result iterator
* @param <T> iterator element type
* @return a ResultIterable
*/
static <T> ResultIterable<T> of(ResultIterator<T> iterator) {
return () -> iterator;
}
/**
* Stream all the rows of the result set out
* with an {@code Iterator}. The {@code Iterator} must be
* closed to release database resources.
* @return the results as a streaming Iterator
*/
@Override
ResultIterator<T> iterator();
/**
* Returns a {@code ResultIterable<U>} derived from this {@code ResultIterable<T>}, by
* transforming elements using the given mapper function.
*
* @param mapper function to apply to elements of this ResultIterable
* @param <U> Element type of the returned ResultIterable
* @return the new ResultIterable
*/
default <U> ResultIterable<U> map(Function<? super T, ? extends U> mapper) {
return () -> new ResultIterator<U>() {
private final ResultIterator<T> delegate = iterator();
@Override
public boolean hasNext() {
return delegate.hasNext();
}
@Override
public U next() {
return mapper.apply(delegate.next());
}
@Override
public StatementContext getContext() {
return delegate.getContext();
}
@Override
public void close() {
delegate.close();
}
};
}
@Override
default void forEach(Consumer<? super T> action) {
try (ResultIterator<T> iterator = iterator()) {
iterator.forEachRemaining(action);
}
}
/**
* Returns the only row in the result set. Returns {@code null} if the row itself is
* {@code null}.
* @throws IllegalStateException if the result set contains zero or multiple rows
* @return the only row in the result set.
*/
default T one() {
try (ResultIterator<T> iter = iterator()) {
if (!iter.hasNext()) {
throw new IllegalStateException("Expected one element, but found none");
}
final T r = iter.next();
if (iter.hasNext()) {
throw new IllegalStateException("Expected one element, but found multiple");
}
return r;
}
}
/**
* Returns the only row in the result set, if any. Returns {@code Optional.empty()} if zero
* rows are returned, or if the row itself is {@code null}.
* @throws IllegalStateException if the result set contains multiple rows
* @return the only row in the result set, if any.
*/
default Optional<T> findOne() {
try (ResultIterator<T> iter = iterator()) {
if (!iter.hasNext()) {
return Optional.empty();
}
final T r = iter.next();
if (iter.hasNext()) {
throw new IllegalStateException("Expected zero to one elements, but found multiple");
}
return Optional.ofNullable(r);
}
}
/**
* Get the only row in the result set.
* @throws IllegalStateException if zero or multiple rows are returned
* @return the object mapped from the singular row in the results
* @deprecated use {@link #one()} or {@link #findOne()} instead.
*/
@Deprecated
default T findOnly() {
return one();
}
/**
* Returns the first row in the result set. Returns {@code null} if the row itself is
* {@code null}.
* @throws IllegalStateException if zero rows are returned
* @return the first row in the result set.
*/
default T first() {
try (ResultIterator<T> iter = iterator()) {
if (!iter.hasNext()) {
throw new IllegalStateException("Expected at least one element, but found none");
}
return iter.next();
}
}
/**
* Returns the first row in the result set, if present. Returns {@code Optional.empty()} if
* zero rows are returned or the first row is {@code null}.
* @return the first row in the result set, if present.
*/
default Optional<T> findFirst() {
try (ResultIterator<T> iter = iterator()) {
return iter.hasNext() ? Optional.ofNullable(iter.next()) : Optional.empty();
}
}
/**
* Returns the stream of results.
*
* <p>
* Note: the returned stream owns database resources, and must be closed via a call to {@link Stream#close()}, or
* by using the stream in a try-with-resources block:
* </p>
*
* <pre>
* try (Stream<T> stream = query.stream()) {
* // do stuff with stream
* }
* </pre>
*
* @return the stream of results.
*
* @see #useStream(StreamConsumer)
* @see #withStream(StreamCallback)
*/
default Stream<T> stream() {
ResultIterator<T> iterator = iterator();
return StreamSupport.stream(spliteratorUnknownSize(iterator, 0), false)
.onClose(iterator::close);
}
/**
* Passes the stream of results to the consumer. Database resources owned by the query are
* released before this method returns.
*
* @param consumer a consumer which receives the stream of results.
* @param <X> the exception type thrown by the callback, if any
*
* @throws X any exception thrown by the callback
*/
default <X extends Exception> void useStream(StreamConsumer<T, X> consumer) throws X {
withStream(stream -> {
consumer.useStream(stream);
return null;
});
}
/**
* Passes the stream of results to the callback. Database resources owned by the query are
* released before this method returns.
*
* @param callback a callback which receives the stream of results, and returns some result.
* @param <R> the type returned by the callback
* @param <X> the exception type thrown by the callback, if any
*
* @return the value returned by the callback.
*
* @throws X any exception thrown by the callback
*/
default <R, X extends Exception> R withStream(StreamCallback<T, R, X> callback) throws X {
try (Stream<T> stream = stream()) {
return callback.withStream(stream);
}
}
/**
* Returns results in a {@link List}.
*
* @return results in a {@link List}.
*/
default List<T> list() {
return collect(Collectors.toList());
}
/**
* Collect the results into a container specified by a collector.
*
* @param collector the collector
* @param <R> the generic type of the container
* @return the container with the query result
*/
default <R> R collect(Collector<? super T, ?, R> collector) {
try (Stream<T> stream = stream()) {
return stream.collect(collector);
}
}
/**
* Reduce the results. Using a {@code BiFunction<U, T, U>}, repeatedly
* combine query results until only a single value remains.
*
* @param <U> the accumulator type
* @param identity the {@code U} to combine with the first result
* @param accumulator the function to apply repeatedly
* @return the final {@code U}
*/
default <U> U reduce(U identity, BiFunction<U, T, U> accumulator) {
try (Stream<T> stream = stream()) {
return stream.reduce(identity, accumulator,
(u, v) -> {
throw new UnsupportedOperationException("parallel operation not supported");
});
}
}
}