Skip to content

Commit eb7d5a2

Browse files
committed
feat: stream() and list() objects in collection
CursorSpliterator powers 2 patterns for iterating over objects: - list() returns an Iterable that can be used in a for-loop - stream() presents the internal Iterator via a familiar Stream API
1 parent 2ed24a4 commit eb7d5a2

3 files changed

Lines changed: 145 additions & 0 deletions

File tree

Lines changed: 63 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,63 @@
1+
package io.weaviate.integration;
2+
3+
import static org.junit.Assume.assumeTrue;
4+
5+
import java.io.IOException;
6+
import java.util.ArrayList;
7+
import java.util.Collections;
8+
9+
import org.assertj.core.api.Assertions;
10+
import org.junit.Test;
11+
12+
import io.weaviate.ConcurrentTest;
13+
import io.weaviate.client6.v1.api.WeaviateClient;
14+
import io.weaviate.client6.v1.api.collections.WeaviateMetadata;
15+
import io.weaviate.client6.v1.api.collections.WeaviateObject;
16+
import io.weaviate.containers.Container;
17+
18+
public class PaginationITest extends ConcurrentTest {
19+
private static WeaviateClient client = Container.WEAVIATE.getClient();
20+
21+
@Test
22+
public void test_stream() throws IOException {
23+
// Arrange
24+
var nsThings = ns("Things");
25+
var count = 10;
26+
27+
client.collections.create(nsThings);
28+
var things = client.collections.use(nsThings);
29+
30+
var inserted = new ArrayList<String>();
31+
for (var i = 0; i < count; i++) {
32+
var object = things.data.insert(Collections.emptyMap());
33+
inserted.add(object.metadata().uuid());
34+
}
35+
assumeTrue("all objects were inserted", inserted.size() == count);
36+
37+
// Act: stream
38+
var gotStream = things.stream()
39+
.map(WeaviateObject::metadata).map(WeaviateMetadata::uuid).toList();
40+
41+
// Assert
42+
Assertions.assertThat(gotStream)
43+
.as("stream fetched all objects")
44+
.hasSize(inserted.size())
45+
.containsAll(inserted);
46+
47+
// Act: list
48+
var gotList = new ArrayList<String>();
49+
for (var object : things.list()) {
50+
gotList.add(object.metadata().uuid());
51+
}
52+
53+
// Assert
54+
Assertions.assertThat(gotList)
55+
.as("list fetched all objects")
56+
.hasSize(inserted.size())
57+
.containsAll(inserted);
58+
59+
Assertions.assertThat(gotStream)
60+
.as("stream and list return consistent order")
61+
.containsExactlyElementsOf(gotList);
62+
}
63+
}

src/main/java/io/weaviate/client6/v1/api/collections/CollectionHandle.java

Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,14 @@
11
package io.weaviate.client6.v1.api.collections;
22

3+
import java.util.Spliterator;
4+
import java.util.Spliterators;
5+
import java.util.stream.Stream;
6+
import java.util.stream.StreamSupport;
7+
38
import io.weaviate.client6.v1.api.collections.aggregate.WeaviateAggregateClient;
49
import io.weaviate.client6.v1.api.collections.config.WeaviateConfigClient;
510
import io.weaviate.client6.v1.api.collections.data.WeaviateDataClient;
11+
import io.weaviate.client6.v1.api.collections.query.QueryMetadata;
612
import io.weaviate.client6.v1.api.collections.query.WeaviateQueryClient;
713
import io.weaviate.client6.v1.internal.grpc.GrpcTransport;
814
import io.weaviate.client6.v1.internal.orm.CollectionDescriptor;
@@ -24,4 +30,18 @@ public CollectionHandle(
2430
this.data = new WeaviateDataClient<>(collectionDescriptor, restTransport, this.query);
2531
this.aggregate = new WeaviateAggregateClient(collectionDescriptor, grpcTransport);
2632
}
33+
34+
public Stream<WeaviateObject<T, Object, QueryMetadata>> stream() {
35+
return StreamSupport.stream(spliterator(2), false);
36+
}
37+
38+
public Iterable<WeaviateObject<T, Object, QueryMetadata>> list() {
39+
return () -> Spliterators.iterator(spliterator(2));
40+
}
41+
42+
private Spliterator<WeaviateObject<T, Object, QueryMetadata>> spliterator(int batchSize) {
43+
return new CursorSpliterator<>(batchSize,
44+
(after, limit) -> this.query.fetchObjects(
45+
query -> query.after(after).limit(limit)).objects());
46+
}
2747
}
Lines changed: 62 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,62 @@
1+
package io.weaviate.client6.v1.api.collections;
2+
3+
import java.util.Collections;
4+
import java.util.Iterator;
5+
import java.util.List;
6+
import java.util.Spliterator;
7+
import java.util.function.BiFunction;
8+
import java.util.function.Consumer;
9+
10+
import io.weaviate.client6.v1.api.collections.query.QueryMetadata;
11+
12+
class CursorSpliterator<T> implements Spliterator<WeaviateObject<T, Object, QueryMetadata>> {
13+
private final int batchSize;
14+
private final BiFunction<String, Integer, List<WeaviateObject<T, Object, QueryMetadata>>> fetch;
15+
16+
// Spliterators do not promise thread-safety, so there's no mechanism
17+
// to protect access to its internal state.
18+
private String cursor;
19+
private Iterator<WeaviateObject<T, Object, QueryMetadata>> currentPage = Collections.emptyIterator();
20+
21+
public CursorSpliterator(int batchSize,
22+
BiFunction<String, Integer, List<WeaviateObject<T, Object, QueryMetadata>>> fetch) {
23+
this.batchSize = batchSize;
24+
this.fetch = fetch;
25+
}
26+
27+
@Override
28+
public boolean tryAdvance(Consumer<? super WeaviateObject<T, Object, QueryMetadata>> action) {
29+
// Happy path: there are remaining objects in the current page.
30+
if (currentPage.hasNext()) {
31+
action.accept(currentPage.next());
32+
return true;
33+
}
34+
35+
// It's OK for the cursor to be null, because it's String (object).
36+
var nextPage = fetch.apply(cursor, batchSize);
37+
if (nextPage.isEmpty()) {
38+
return false;
39+
}
40+
cursor = nextPage.get(nextPage.size() - 1).metadata().uuid();
41+
currentPage = nextPage.iterator();
42+
return tryAdvance(action);
43+
}
44+
45+
@Override
46+
public Spliterator<WeaviateObject<T, Object, QueryMetadata>> trySplit() {
47+
// Do not support splitting just now;
48+
return null;
49+
}
50+
51+
@Override
52+
public long estimateSize() {
53+
// CursorSpliterator does not have SIZED characteristic, so this is our
54+
// best-effort estimate. The number of objects in the db is unbounded.
55+
return Long.MAX_VALUE;
56+
}
57+
58+
@Override
59+
public int characteristics() {
60+
return ORDERED | NONNULL;
61+
}
62+
}

0 commit comments

Comments
 (0)