diff --git a/pom.xml b/pom.xml index 32a819d4..8340230c 100644 --- a/pom.xml +++ b/pom.xml @@ -61,7 +61,7 @@ 3.27.4 1.0.4 5.19.0 - 2.0.17 + 1.7.36 1.5.18 5.14.0 2.19.2 diff --git a/src/it/java/io/weaviate/integration/SearchITest.java b/src/it/java/io/weaviate/integration/SearchITest.java index 05750184..f7b64ac0 100644 --- a/src/it/java/io/weaviate/integration/SearchITest.java +++ b/src/it/java/io/weaviate/integration/SearchITest.java @@ -29,6 +29,7 @@ import io.weaviate.client6.v1.api.collections.query.Metadata; import io.weaviate.client6.v1.api.collections.query.QueryMetadata; import io.weaviate.client6.v1.api.collections.query.QueryResponseGroup; +import io.weaviate.client6.v1.api.collections.query.SortBy; import io.weaviate.client6.v1.api.collections.query.Where; import io.weaviate.containers.Container; import io.weaviate.containers.Container.ContainerGroup; @@ -257,6 +258,72 @@ public void testFetchObjectsWithFilters() throws IOException { } + @Test + public void testFetchObjectsWithSort() throws Exception { + var nsNumbers = ns("Numbers"); + + // Arrange + client.collections.create(nsNumbers, + c -> c.properties(Property.integer("value"))); + + var numbers = client.collections.use(nsNumbers); + + var one = numbers.data.insert(Map.of("value", 1L)); + var two = numbers.data.insert(Map.of("value", 2L)); + var three = numbers.data.insert(Map.of("value", 3L)); + + // Act: sort ascending + var asc = numbers.query.fetchObjects( + q -> q.sort(SortBy.property("value"))); + + Assertions.assertThat(asc.objects()) + .as("value asc") + .hasSize(3) + .extracting(WeaviateObject::properties) + .extracting(object -> object.get("value")) + .containsExactly(1L, 2L, 3L); + + // Act: sort descending + var desc = numbers.query.fetchObjects( + q -> q.sort(SortBy.property("value").desc())); + + Assertions.assertThat(desc.objects()) + .as("value desc") + .hasSize(3) + .extracting(WeaviateObject::properties) + .extracting(object -> object.get("value")) + .containsExactly(3L, 2L, 1L); + + // Act: sort by creation time asc + var created = numbers.query.fetchObjects( + q -> q.sort(SortBy.creationTime())); + + Assertions.assertThat(created.objects()) + .as("create time asc") + .hasSize(3) + .extracting(WeaviateObject::uuid) + .containsExactly(one.uuid(), two.uuid(), three.uuid()); + + // Act: sort by updated time desc + numbers.data.update(one.uuid(), upd -> upd.properties(Map.of("value", -1L))); + Thread.sleep(10); + numbers.data.update(two.uuid(), upd -> upd.properties(Map.of("value", -2L))); + Thread.sleep(10); + numbers.data.update(three.uuid(), upd -> upd.properties(Map.of("value", -3L))); + + var updated = numbers.query.fetchObjects( + q -> q.sort( + // Both sort operators imply ordering 3-2-1 + SortBy.lastUpdateTime().desc(), + SortBy.property("value").asc())); + + Assertions.assertThat(updated.objects()) + .as("last update time desc + value asc") + .hasSize(3) + .extracting(WeaviateObject::uuid) + .containsExactly(three.uuid(), two.uuid(), one.uuid()); + } + @Test public void testBm25() throws IOException, InterruptedException, ExecutionException { var nsWords = ns("Words"); diff --git a/src/main/java/io/weaviate/client6/v1/api/collections/query/FetchObjects.java b/src/main/java/io/weaviate/client6/v1/api/collections/query/FetchObjects.java index af55b4ce..e132497d 100644 --- a/src/main/java/io/weaviate/client6/v1/api/collections/query/FetchObjects.java +++ b/src/main/java/io/weaviate/client6/v1/api/collections/query/FetchObjects.java @@ -1,21 +1,54 @@ package io.weaviate.client6.v1.api.collections.query; +import java.util.ArrayList; +import java.util.Arrays; +import java.util.List; import java.util.function.Function; import io.weaviate.client6.v1.internal.ObjectBuilder; import io.weaviate.client6.v1.internal.grpc.protocol.WeaviateProtoSearchGet; -public record FetchObjects(BaseQueryOptions common) implements QueryOperator { +public record FetchObjects(BaseQueryOptions common, List sortBy) implements QueryOperator { public static FetchObjects of(Function> fn) { return fn.apply(new Builder()).build(); } public FetchObjects(Builder builder) { - this(builder.baseOptions()); + this(builder.baseOptions(), builder.sortBy); } public static class Builder extends BaseQueryOptions.Builder { + private final List sortBy = new ArrayList<>(); + + /** + * Sort query results. Default sorted order is ascending, use + * {@link SortBy#desc} to reverse it. + * + *
{@code
+     * sort(SortBy.property("age"), SortBy.creationTime().desc());
+     * }
+ * + * @param sortBy A list of sort-by clauses in the order + * they should be applied. + * @return This builder. + */ + public Builder sort(SortBy... sortBy) { + return sort(Arrays.asList(sortBy)); + } + + /** + * Sort query results. Default sorted order is ascending, use + * {@link SortBy#desc} to reverse it. + * + * @param sortBy A list of sort-by clauses in the order + * they should be applied. + * @return This builder. + */ + public Builder sort(List sortBy) { + this.sortBy.addAll(sortBy); + return this; + } @Override public final FetchObjects build() { @@ -26,5 +59,11 @@ public final FetchObjects build() { @Override public void appendTo(WeaviateProtoSearchGet.SearchRequest.Builder req) { common.appendTo(req); + + for (final var sort : sortBy) { + req.addSortBy(WeaviateProtoSearchGet.SortBy.newBuilder() + .addAllPath(sort.path()) + .setAscending(sort.ascending())); + } } } diff --git a/src/main/java/io/weaviate/client6/v1/api/collections/query/SortBy.java b/src/main/java/io/weaviate/client6/v1/api/collections/query/SortBy.java new file mode 100644 index 00000000..6c9d3817 --- /dev/null +++ b/src/main/java/io/weaviate/client6/v1/api/collections/query/SortBy.java @@ -0,0 +1,60 @@ +package io.weaviate.client6.v1.api.collections.query; + +import java.util.List; + +public record SortBy(List path, boolean ascending) { + /** + * Sort by object property. Ascending order by default. + * + * @see #desc() to sort in descending order. + */ + public static SortBy property(String property) { + return new SortBy(List.of(property), true); + } + + /** + * Sort by object creation time. Ascending order by default. + * + * @see #desc() to sort in descending order. + */ + public static SortBy creationTime() { + return property("_creationTimeUnix"); + } + + /** + * Sort by object last update time. Ascending order by default. + * + * @see #desc() to sort in descending order. + */ + public static SortBy lastUpdateTime() { + return property("_lastUpdateTimeUnix"); + } + + /** + * Sort in ascending order. + * + *

+ * Example: + * + *

{@code
+   * SortBy.property("name").asc();
+   * }
+ */ + public SortBy asc() { + return new SortBy(path, true); + } + + /** + * Sort in descending order. + * + *

+ * Example: + * + *

{@code
+   * SortBy.property("name").desc();
+   * }
+ */ + public SortBy desc() { + return new SortBy(path, false); + } +}