diff --git a/src/it/java/io/weaviate/integration/SearchITest.java b/src/it/java/io/weaviate/integration/SearchITest.java index 41066b28d..7359057ae 100644 --- a/src/it/java/io/weaviate/integration/SearchITest.java +++ b/src/it/java/io/weaviate/integration/SearchITest.java @@ -1,6 +1,7 @@ package io.weaviate.integration; import java.io.IOException; +import java.time.OffsetDateTime; import java.util.Collections; import java.util.Comparator; import java.util.HashMap; @@ -31,13 +32,13 @@ import io.weaviate.client6.v1.api.collections.generate.GenerativeObject; import io.weaviate.client6.v1.api.collections.generate.TaskOutput; import io.weaviate.client6.v1.api.collections.generative.DummyGenerative; +import io.weaviate.client6.v1.api.collections.query.Filter; import io.weaviate.client6.v1.api.collections.query.GroupBy; 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.Target; -import io.weaviate.client6.v1.api.collections.query.Filter; import io.weaviate.client6.v1.api.collections.vectorindex.Hnsw; import io.weaviate.client6.v1.api.collections.vectorindex.MultiVector; import io.weaviate.containers.Container; @@ -609,9 +610,9 @@ public void testGenerative_bm25() throws IOException { .hasSize(2) .allSatisfy(obj -> { Assertions.assertThat(obj).as("uuid shorthand") - .returns(obj.uuid(), GenerativeObject::uuid); + .returns(obj.uuid(), GenerativeObject::uuid); Assertions.assertThat(obj).as("vectors shorthand") - .returns(obj.vectors(), GenerativeObject::vectors); + .returns(obj.vectors(), GenerativeObject::vectors); }) .extracting(GenerativeObject::generative) .allSatisfy(generated -> { @@ -675,6 +676,72 @@ public void testGenerative_bm25_groupBy() throws IOException { .isNotBlank(); } + @Test + public void test_filterIsNull() throws IOException { + // Arrange + var nsNulls = ns("Nulls"); + + var nulls = client.collections.create(nsNulls, + c -> c + .invertedIndex(idx -> idx.indexNulls(true)) + .properties(Property.text("never"))); + + var inserted = nulls.data.insertMany(Map.of(), Map.of("never", "notNull")); + Assertions.assertThat(inserted.errors()).isEmpty(); + + // Act + var isNull = nulls.query.fetchObjects(q -> q.filters(Filter.property("never").isNull())); + var isNotNull = nulls.query.fetchObjects(q -> q.filters(Filter.property("never").isNotNull())); + + // Assert + var isNull_1 = Assertions.assertThat(isNull.objects()) + .as("objects WHERE never IS NULL") + .hasSize(1).first().actual(); + var isNotNull_1 = Assertions.assertThat(isNotNull.objects()) + .as("objects WHERE never IS NOT NULL") + .hasSize(1).first().actual(); + Assertions.assertThat(isNull_1).isNotEqualTo(isNotNull_1); + } + + @Test + public void test_filterCreateUpdateTime() throws IOException { + // Arrange + var now = OffsetDateTime.now().minusHours(1); + var nsCounter = ns("Counter"); + + var counter = client.collections.create(nsCounter, + c -> c + .invertedIndex(idx -> idx.indexTimestamps(true)) + .properties(Property.integer("count"))); + + counter.data.insert(Map.of("count", 0)); + + // Act + var beforeNow = counter.query.fetchObjects(q -> q.filters(Filter.createdAt().lt(now))); + var afterNow = counter.query.fetchObjects(q -> q.filters(Filter.createdAt().gt(now))); + + // Assert + Assertions.assertThat(beforeNow.objects()).isEmpty(); + Assertions.assertThat(afterNow.objects()).hasSize(1); + } + + @Test + public void teset_filterPropertyLength() throws IOException { + // Arrange + var nsStrings = ns("Strings"); + + var strings = client.collections.create(nsStrings, c -> c + .invertedIndex(idx -> idx.indexPropertyLength(true)) + .properties(Property.text("letters"))); + strings.data.insertMany(Map.of("letters", "abc"), Map.of("letters", "abcd"), Map.of("letters", "a")); + + // Act + var got = strings.query.fetchObjects(q -> q.filters(Filter.propertyLen("letters").gte(3))); + + // Assertions + Assertions.assertThat(got.objects()).hasSize(2); + } + /** * Ensure the client respects server's configuration for max gRPC size: * we create a server with 1-byte message size and try to send a large payload @@ -700,6 +767,5 @@ public void test_maxGrpcMessageSize() throws Exception { huge.data.insertMany(hugeObject); }).isInstanceOf(io.grpc.StatusRuntimeException.class); } - System.out.println("here?"); } } diff --git a/src/main/java/io/weaviate/client6/v1/api/collections/query/BaseQueryOptions.java b/src/main/java/io/weaviate/client6/v1/api/collections/query/BaseQueryOptions.java index 64276e425..1786ce74d 100644 --- a/src/main/java/io/weaviate/client6/v1/api/collections/query/BaseQueryOptions.java +++ b/src/main/java/io/weaviate/client6/v1/api/collections/query/BaseQueryOptions.java @@ -26,6 +26,10 @@ public record BaseQueryOptions( List returnMetadata, List includeVectors) { + static final String ID_PROPERTY = "_id"; + static final String CREATION_TIME_PROPERTY = "_creationTimeUnix"; + static final String LAST_UPDATE_TIME_PROPERTY = "_lastUpdateTimeUnix"; + private BaseQueryOptions(Builder, T> builder) { this( builder.limit, diff --git a/src/main/java/io/weaviate/client6/v1/api/collections/query/FetchObjectById.java b/src/main/java/io/weaviate/client6/v1/api/collections/query/FetchObjectById.java index 916edbb0d..3e3bdf512 100644 --- a/src/main/java/io/weaviate/client6/v1/api/collections/query/FetchObjectById.java +++ b/src/main/java/io/weaviate/client6/v1/api/collections/query/FetchObjectById.java @@ -19,8 +19,6 @@ public record FetchObjectById( List returnMetadata, List includeVectors) implements QueryOperator { - static final String ID_PROPERTY = "_id"; - public static FetchObjectById of(String uuid) { return of(uuid, ObjectBuilder.identity()); } diff --git a/src/main/java/io/weaviate/client6/v1/api/collections/query/Filter.java b/src/main/java/io/weaviate/client6/v1/api/collections/query/Filter.java index b7e9e6402..6a2dc38a0 100644 --- a/src/main/java/io/weaviate/client6/v1/api/collections/query/Filter.java +++ b/src/main/java/io/weaviate/client6/v1/api/collections/query/Filter.java @@ -18,10 +18,11 @@ private enum Operator { // Comparison operators EQUAL("Equal", WeaviateProtoBase.Filters.Operator.OPERATOR_EQUAL), NOT_EQUAL("NotEqual", WeaviateProtoBase.Filters.Operator.OPERATOR_NOT_EQUAL), - LESS_THAN("LessThen", WeaviateProtoBase.Filters.Operator.OPERATOR_LESS_THAN), + LESS_THAN("LessThan", WeaviateProtoBase.Filters.Operator.OPERATOR_LESS_THAN), LESS_THAN_EQUAL("LessThenEqual", WeaviateProtoBase.Filters.Operator.OPERATOR_LESS_THAN_EQUAL), - GREATER_THAN("GreaterThen", WeaviateProtoBase.Filters.Operator.OPERATOR_GREATER_THAN), - GREATER_THAN_EQUAL("GreaterThenEqual", WeaviateProtoBase.Filters.Operator.OPERATOR_GREATER_THAN_EQUAL), + GREATER_THAN("GreaterThan", WeaviateProtoBase.Filters.Operator.OPERATOR_GREATER_THAN), + GREATER_THAN_EQUAL("GreaterThanEqual", WeaviateProtoBase.Filters.Operator.OPERATOR_GREATER_THAN_EQUAL), + IS_NULL("IsNull", WeaviateProtoBase.Filters.Operator.OPERATOR_IS_NULL), LIKE("Like", WeaviateProtoBase.Filters.Operator.OPERATOR_LIKE), CONTAINS_ANY("ContainsAny", WeaviateProtoBase.Filters.Operator.OPERATOR_CONTAINS_ANY), CONTAINS_ALL("ContainsAll", WeaviateProtoBase.Filters.Operator.OPERATOR_CONTAINS_ALL), @@ -109,18 +110,28 @@ public Filter not() { // -------------------------------------------------------------------------- /** Filter by object UUID. */ - public static FilterBuilder uuid() { - return property(FetchObjectById.ID_PROPERTY); + public static UuidProperty uuid() { + return new UuidProperty(); + } + + /** Filter by object creation time. */ + public static DateProperty createdAt() { + return new DateProperty(BaseQueryOptions.CREATION_TIME_PROPERTY); + } + + /** Filter by object last update time. */ + public static DateProperty lastUpdatedAt() { + return new DateProperty(BaseQueryOptions.LAST_UPDATE_TIME_PROPERTY); } /** Filter by object property. */ public static FilterBuilder property(String property) { - return new FilterBuilder(new PathOperand(property)); + return new FilterBuilder(new PathOperand(false, property)); } - /** Filter by a property of the referenced object. */ - public static FilterBuilder reference(String... path) { - return new FilterBuilder(new PathOperand(path)); + /** Filter by object property's length. */ + public static FilterBuilder propertyLen(String property) { + return new FilterBuilder(new PathOperand(true, property)); } public static class FilterBuilder { @@ -422,6 +433,20 @@ public Filter gte(Object value) { return new Filter(Operator.GREATER_THAN_EQUAL, left, fromObject(value)); } + // IsNull + // ------------------------------------------------------------------------ + public Filter isNull() { + return isNull(true); + } + + public Filter isNotNull() { + return isNull(false); + } + + public Filter isNull(boolean isNull) { + return new Filter(Operator.IS_NULL, left, new BooleanOperand(isNull)); + } + // Like // ------------------------------------------------------------------------ public Filter like(String value) { @@ -596,20 +621,26 @@ static FilterOperand fromObject(Object value) { private static class PathOperand implements FilterOperand { private final List path; + private final boolean length; - private PathOperand(List path) { + private PathOperand(boolean length, List path) { this.path = path; + this.length = length; } - private PathOperand(String... path) { - this(Arrays.asList(path)); + private PathOperand(boolean length, String... path) { + this(length, Arrays.asList(path)); } @Override public void appendTo(WeaviateProtoBase.Filters.Builder filter) { - // "on" is deprecated, but the current proto doesn't have "path". if (!path.isEmpty()) { - filter.addOn(path.get(0)); + var property = path.get(0); + if (length) { + property = "len(" + property + ")"; + } + filter.setTarget(WeaviateProtoBase.FilterTarget.newBuilder() + .setProperty(property)); } // FIXME: no way to reference objects rn? } @@ -620,6 +651,82 @@ public String toString() { } } + public static class UuidProperty extends PathOperand { + private UuidProperty() { + super(false, BaseQueryOptions.ID_PROPERTY); + } + + public Filter eq(String value) { + return new Filter(Operator.EQUAL, this, new TextOperand(value)); + } + + public Filter ne(String value) { + return new Filter(Operator.NOT_EQUAL, this, new TextOperand(value)); + } + + public Filter gt(String value) { + return new Filter(Operator.GREATER_THAN, this, new TextOperand(value)); + } + + public Filter gte(String value) { + return new Filter(Operator.GREATER_THAN_EQUAL, this, new TextOperand(value)); + } + + public Filter lt(String value) { + return new Filter(Operator.LESS_THAN, this, new TextOperand(value)); + } + + public Filter lte(String value) { + return new Filter(Operator.LESS_THAN_EQUAL, this, new TextOperand(value)); + } + + public Filter containsAny(String... values) { + return new Filter(Operator.CONTAINS_ANY, this, new TextArrayOperand(values)); + } + + public Filter containsNone(String... values) { + return new Filter(Operator.CONTAINS_NONE, this, new TextArrayOperand(values)); + } + } + + public static class DateProperty extends PathOperand { + private DateProperty(String propertyName) { + super(false, propertyName); + } + + public Filter eq(OffsetDateTime value) { + return new Filter(Operator.EQUAL, this, new DateOperand(value)); + } + + public Filter ne(OffsetDateTime value) { + return new Filter(Operator.NOT_EQUAL, this, new DateOperand(value)); + } + + public Filter gt(OffsetDateTime value) { + return new Filter(Operator.GREATER_THAN, this, new DateOperand(value)); + } + + public Filter gte(OffsetDateTime value) { + return new Filter(Operator.GREATER_THAN_EQUAL, this, new DateOperand(value)); + } + + public Filter lt(OffsetDateTime value) { + return new Filter(Operator.LESS_THAN, this, new DateOperand(value)); + } + + public Filter lte(OffsetDateTime value) { + return new Filter(Operator.LESS_THAN_EQUAL, this, new DateOperand(value)); + } + + public Filter containsAny(OffsetDateTime... values) { + return new Filter(Operator.CONTAINS_ANY, this, new DateArrayOperand(values)); + } + + public Filter containsNone(OffsetDateTime... values) { + return new Filter(Operator.CONTAINS_NONE, this, new DateArrayOperand(values)); + } + } + private static class TextOperand implements FilterOperand { private final String value; 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 index 55ca1b3e4..ebab47aca 100644 --- 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 @@ -26,7 +26,7 @@ public static SortBy property(String property) { * @see #desc() to sort in descending order. */ public static SortBy uuid() { - return property(FetchObjectById.ID_PROPERTY); + return property(BaseQueryOptions.ID_PROPERTY); } /**