Skip to content

Commit b7e9579

Browse files
committed
feat: add additional information for exceptions during pagination
1 parent 0e45ae7 commit b7e9579

6 files changed

Lines changed: 102 additions & 26 deletions

File tree

src/it/java/io/weaviate/integration/PaginationITest.java

Lines changed: 33 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@
66
import java.util.ArrayList;
77
import java.util.Collections;
88
import java.util.concurrent.CompletableFuture;
9+
import java.util.concurrent.CompletionException;
910
import java.util.concurrent.ExecutionException;
1011
import java.util.concurrent.atomic.AtomicInteger;
1112

@@ -18,6 +19,7 @@
1819
import io.weaviate.client6.v1.api.collections.Property;
1920
import io.weaviate.client6.v1.api.collections.WeaviateMetadata;
2021
import io.weaviate.client6.v1.api.collections.WeaviateObject;
22+
import io.weaviate.client6.v1.api.collections.pagination.WeaviatePaginationException;
2123
import io.weaviate.containers.Container;
2224

2325
public class PaginationITest extends ConcurrentTest {
@@ -89,7 +91,7 @@ public void testResumePagination() throws IOException {
8991
.reduce((prev, next) -> next).get();
9092

9193
// Act
92-
var remaining = things.paginate(p -> p.resumeFrom(lastId)).stream().count();
94+
var remaining = things.paginate(p -> p.fromCursor(lastId)).stream().count();
9395

9496
// Assert
9597
Assertions.assertThat(remaining).isEqualTo(5);
@@ -157,4 +159,34 @@ public void testAsyncPaginator() throws IOException, InterruptedException, Execu
157159
.isEqualTo(count);
158160
}
159161
}
162+
163+
@Test(expected = WeaviatePaginationException.class)
164+
public void testFailedPagination() throws IOException {
165+
var things = client.collections.use("Unknown");
166+
things.paginate().forEach(System.out::println);
167+
}
168+
169+
@Test(expected = WeaviatePaginationException.class)
170+
public void testFailedAsyncPagination_forEach() throws Throwable {
171+
try (final var async = client.async()) {
172+
var things = async.collections.use("Unknown");
173+
try {
174+
things.paginate().forEach(__ -> System.out.println("called once")).join();
175+
} catch (CompletionException e) {
176+
throw e.getCause(); // CompletableFuture exceptions are always wrapped
177+
}
178+
}
179+
}
180+
181+
@Test(expected = WeaviatePaginationException.class)
182+
public void testFailedAsyncPagination_forPage() throws Throwable {
183+
try (final var async = client.async()) {
184+
var things = async.collections.use("Unknown");
185+
try {
186+
things.paginate().forPage(__ -> System.out.println("called once")).join();
187+
} catch (CompletionException e) {
188+
throw e.getCause(); // CompletableFuture exceptions are always wrapped
189+
}
190+
}
191+
}
160192
}

src/it/java/io/weaviate/integration/SearchITest.java

Lines changed: 6 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@
66
import java.util.HashMap;
77
import java.util.List;
88
import java.util.Map;
9+
import java.util.concurrent.CompletionException;
910
import java.util.concurrent.ExecutionException;
1011

1112
import org.assertj.core.api.Assertions;
@@ -402,15 +403,15 @@ public void testBadRequest_async() throws Throwable {
402403
collection -> collection
403404
.properties(Property.text("name"))
404405
.vectors(Vectorizers.text2vecContextionary()))
405-
.get();
406+
.join();
406407

407408
var things = async.collections.use(nsThings);
408-
var balloon = things.data.insert(Map.of("name", "balloon")).get();
409+
var balloon = things.data.insert(Map.of("name", "balloon")).join();
409410

410411
try {
411-
things.query.nearObject(balloon.uuid(), q -> q.limit(-1)).get();
412-
} catch (ExecutionException e) {
413-
throw e.getCause();
412+
things.query.nearObject(balloon.uuid(), q -> q.limit(-1)).join();
413+
} catch (CompletionException e) {
414+
throw e.getCause(); // CompletableFuture exceptions are always wrapped
414415
}
415416
}
416417
}

src/main/java/io/weaviate/client6/v1/api/WeaviateApiException.java

Lines changed: 27 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -9,8 +9,8 @@ public class WeaviateApiException extends RuntimeException {
99
private final String errorMessage;
1010
private final Source source;
1111
private final String endpoint;
12-
private final Integer statusCode;
13-
private final String grpcStatus;
12+
private final Integer httpStatusCode;
13+
private final io.grpc.Status.Code grpcStatusCode;
1414

1515
private enum Source {
1616
HTTP, GRPC;
@@ -22,32 +22,48 @@ public static WeaviateApiException http(String method, String endpoint, int stat
2222

2323
public static WeaviateApiException gRPC(io.grpc.StatusRuntimeException ex) {
2424
var status = ex.getStatus();
25-
return new WeaviateApiException(status.getCode().toString(), status.getDescription());
25+
return new WeaviateApiException(status.getCode(), status.getDescription());
2626
}
2727

28-
private WeaviateApiException(String status, String errorMessage) {
29-
super("%s: %s".formatted(status, errorMessage));
28+
private WeaviateApiException(io.grpc.Status.Code code, String errorMessage) {
29+
super("%s: %s".formatted(code, errorMessage));
3030
this.source = Source.GRPC;
3131
this.errorMessage = errorMessage;
32-
this.grpcStatus = status;
32+
this.grpcStatusCode = code;
3333
this.endpoint = null;
34-
this.statusCode = null;
34+
this.httpStatusCode = null;
3535
}
3636

3737
private WeaviateApiException(String method, String endpoint, int statusCode, String errorMessage) {
3838
super("HTTP %d: %s %s: %s".formatted(statusCode, method, endpoint, errorMessage));
3939
this.source = Source.HTTP;
4040
this.errorMessage = errorMessage;
4141
this.endpoint = endpoint;
42-
this.statusCode = statusCode;
43-
this.grpcStatus = null;
42+
this.httpStatusCode = statusCode;
43+
this.grpcStatusCode = null;
44+
}
45+
46+
public boolean isGPRC() {
47+
return source == Source.GRPC;
48+
}
49+
50+
public String grpcStatusCode() {
51+
return grpcStatusCode.toString();
52+
}
53+
54+
public boolean isHTTP() {
55+
return source == Source.HTTP;
4456
}
4557

4658
public String endpoint() {
4759
return endpoint;
4860
}
4961

50-
public Integer statusCode() {
51-
return statusCode;
62+
public Integer httpStatusCode() {
63+
return httpStatusCode;
64+
}
65+
66+
public String getError() {
67+
return errorMessage;
5268
}
5369
}

src/main/java/io/weaviate/client6/v1/api/collections/pagination/AsyncPage.java

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -40,6 +40,15 @@ public boolean isEmpty() {
4040
return this.currentPage.isEmpty();
4141
}
4242

43+
/**
44+
* Fetch an {@link AsyncPage} containing the next {@code pageSize} results
45+
* and advance the cursor.
46+
*
47+
* <p>
48+
* The returned stage may complete exceptionally in case the underlying
49+
* query fails. Callers are advised to use exception-aware
50+
* {@link CompletableFuture#handle} to process page results.
51+
*/
4352
public CompletableFuture<AsyncPage<PropertiesT>> fetchNextPage() {
4453
return fetch.apply(cursor, pageSize)
4554
.thenApply(nextPage -> {

src/main/java/io/weaviate/client6/v1/api/collections/pagination/AsyncPaginator.java

Lines changed: 20 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -31,9 +31,16 @@ public AsyncPaginator(Builder<PropertiesT> builder) {
3131
var rs = new AsyncPage<PropertiesT>(
3232
cursor,
3333
pageSize,
34-
(after, limit) -> {
35-
var fn = ObjectBuilder.partial(queryOptions, q -> q.after(after).limit(limit));
36-
return this.query.fetchObjects(fn).thenApply(QueryResponse::objects);
34+
(cursor, pageSize) -> {
35+
var fn = ObjectBuilder.partial(queryOptions, q -> q.after(cursor).limit(pageSize));
36+
return this.query.fetchObjects(fn)
37+
.handle((response, ex) -> {
38+
if (ex != null) {
39+
throw WeaviatePaginationException.after(cursor, pageSize, ex);
40+
}
41+
return response;
42+
})
43+
.thenApply(QueryResponse::objects);
3744
});
3845

3946
this.resultSet = builder.prefetch ? rs.fetchNextPage() : CompletableFuture.completedFuture(rs);
@@ -51,17 +58,17 @@ public CompletableFuture<Void> forPage(Consumer<List<WeaviateObject<PropertiesT,
5158
.thenCompose(processPageAndAdvance(action));
5259
}
5360

54-
public Function<AsyncPage<PropertiesT>, CompletableFuture<Void>> processEachAndAdvance(
61+
private static <PropertiesT> Function<AsyncPage<PropertiesT>, CompletableFuture<Void>> processEachAndAdvance(
5562
Consumer<WeaviateObject<PropertiesT, Object, QueryMetadata>> action) {
5663
return processAndAdvanceFunc(rs -> rs.forEach(action));
5764
}
5865

59-
public Function<AsyncPage<PropertiesT>, CompletableFuture<Void>> processPageAndAdvance(
66+
private static <PropertiesT> Function<AsyncPage<PropertiesT>, CompletableFuture<Void>> processPageAndAdvance(
6067
Consumer<List<WeaviateObject<PropertiesT, Object, QueryMetadata>>> action) {
6168
return processAndAdvanceFunc(rs -> action.accept(rs.items()));
6269
}
6370

64-
public Function<AsyncPage<PropertiesT>, CompletableFuture<Void>> processAndAdvanceFunc(
71+
private static <PropertiesT> Function<AsyncPage<PropertiesT>, CompletableFuture<Void>> processAndAdvanceFunc(
6572
Consumer<AsyncPage<PropertiesT>> action) {
6673
return rs -> {
6774
// Empty result set means there were no more objects to fetch.
@@ -105,11 +112,17 @@ public Builder<PropertiesT> pageSize(int pageSize) {
105112
return this;
106113
}
107114

108-
public Builder<PropertiesT> resumeFrom(String uuid) {
115+
/** Set a cursor (object UUID) to start pagination from. */
116+
public Builder<PropertiesT> fromCursor(String uuid) {
109117
this.cursor = uuid;
110118
return this;
111119
}
112120

121+
/**
122+
* When prefetch is enabled, the first page is retrieved before any of the
123+
* terminating methods ({@link AsyncPaginator#forEach},
124+
* {@link AsyncPaginator#forPage}) are called on the paginator.
125+
*/
113126
public Builder<PropertiesT> prefetch(boolean enable) {
114127
this.prefetch = enable;
115128
return this;

src/main/java/io/weaviate/client6/v1/api/collections/pagination/Paginator.java

Lines changed: 7 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -37,7 +37,11 @@ public Spliterator<WeaviateObject<PropertiesT, Object, QueryMetadata>> spliterat
3737
return new CursorSpliterator<PropertiesT>(cursor, pageSize,
3838
(after, limit) -> {
3939
var fn = ObjectBuilder.partial(queryOptions, q -> q.after(after).limit(limit));
40-
return query.fetchObjects(fn).objects();
40+
try {
41+
return query.fetchObjects(fn).objects();
42+
} catch (Exception e) {
43+
throw WeaviatePaginationException.after(cursor, pageSize, e);
44+
}
4145
});
4246
}
4347

@@ -75,7 +79,8 @@ public Builder<T> pageSize(int pageSize) {
7579
return this;
7680
}
7781

78-
public Builder<T> resumeFrom(String uuid) {
82+
/** Set a cursor (object UUID) to start pagination from. */
83+
public Builder<T> fromCursor(String uuid) {
7984
this.cursor = uuid;
8085
return this;
8186
}

0 commit comments

Comments
 (0)