diff --git a/pom.xml b/pom.xml index 32a819d49..8340230c2 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/containers/Weaviate.java b/src/it/java/io/weaviate/containers/Weaviate.java index e1bb7157f..8b373e98b 100644 --- a/src/it/java/io/weaviate/containers/Weaviate.java +++ b/src/it/java/io/weaviate/containers/Weaviate.java @@ -92,13 +92,12 @@ public static Weaviate.Builder custom() { public static class Builder { private String versionTag; private Set enableModules = new HashSet<>(); - private boolean telemetry; private Map environment = new HashMap<>(); public Builder() { this.versionTag = VERSION; - this.telemetry = false; + enableAutoSchema(false); } public Builder withVersion(String version) { @@ -143,6 +142,11 @@ public Builder enableTelemetry(boolean enable) { return this; } + public Builder enableAutoSchema(boolean enable) { + environment.put("AUTOSCHEMA_ENABLED", Boolean.toString(!enable)); + return this; + } + public Builder enableAnonymousAccess(boolean enable) { environment.put("AUTHENTICATION_ANONYMOUS_ACCESS_ENABLED", Boolean.toString(enable)); return this; diff --git a/src/it/java/io/weaviate/integration/ORMITest.java b/src/it/java/io/weaviate/integration/ORMITest.java new file mode 100644 index 000000000..d46d1600c --- /dev/null +++ b/src/it/java/io/weaviate/integration/ORMITest.java @@ -0,0 +1,310 @@ +package io.weaviate.integration; + +import java.time.OffsetDateTime; +import java.util.List; +import java.util.Map; +import java.util.UUID; + +import org.assertj.core.api.Assertions; +import org.assertj.core.api.InstanceOfAssertFactories; +import org.assertj.core.api.recursive.comparison.RecursiveComparisonConfiguration; +import org.junit.BeforeClass; +import org.junit.Test; + +import io.weaviate.ConcurrentTest; +import io.weaviate.client6.v1.api.WeaviateClient; +import io.weaviate.client6.v1.api.collections.CollectionConfig; +import io.weaviate.client6.v1.api.collections.annotations.Collection; +import io.weaviate.client6.v1.api.collections.annotations.Property; +import io.weaviate.client6.v1.api.collections.data.InsertManyResponse.InsertObject; +import io.weaviate.client6.v1.api.collections.query.Where; +import io.weaviate.containers.Container; + +public class ORMITest extends ConcurrentTest { + private static WeaviateClient client = Container.WEAVIATE.getClient(); + + @Collection("ORMITest") + static record Thing( + // text / text[] + String text, + String[] textArray, + List textList, + + // date / date[] + OffsetDateTime date, + OffsetDateTime[] dateArray, + List dateList, + + // uuid / uuid[] + UUID uuid, + UUID[] uuidArray, + List uuidList, + + // int / int[] + @Property("short") short short_, + Short shortBoxed, + short[] shortArray, + Short[] shortBoxedArray, + List shortBoxedList, + + @Property("int") int int_, + Integer intBoxed, + int[] intArray, + Integer[] intBoxedArray, + List intBoxedList, + + @Property("long") long long_, + Long longBoxed, + long[] longArray, + Long[] longBoxedArray, + List longBoxedList, + + // number / number[] + @Property("float") float float_, + Float floatBoxed, + float[] floatArray, + Float[] floatBoxedArray, + List floatBoxedList, + + @Property("double") double double_, + Double doubleBoxed, + double[] doubleArray, + Double[] doubleBoxedArray, + List doubleBoxedList, + + // boolean / boolean[] + @Property("boolean") boolean boolean_, + Boolean booleanBoxed, + boolean[] booleanArray, + Boolean[] booleanBoxedArray, + List booleanBoxedList) { + } + + @BeforeClass + public static void setUp() throws Exception { + client.collections.create(Thing.class); + } + + @Test + public void test_createCollection() throws Exception { + // Arrange + var things = client.collections.use(Thing.class); + + // Act + var config = things.config.get(); + + // Assert + Assertions.assertThat(config).get() + .returns("ORMITest", CollectionConfig::collectionName) + .extracting(CollectionConfig::properties, + InstanceOfAssertFactories.list(io.weaviate.client6.v1.api.collections.Property.class)) + .extracting(p -> Map.entry( + p.propertyName(), + p.dataTypes().get(0))) + .contains( + Map.entry("text", "text"), + Map.entry("textArray", "text[]"), + Map.entry("textList", "text[]"), + + Map.entry("date", "date"), + Map.entry("dateArray", "date[]"), + Map.entry("dateList", "date[]"), + + Map.entry("uuid", "uuid"), + Map.entry("uuidArray", "uuid[]"), + Map.entry("uuidList", "uuid[]"), + + Map.entry("short", "int"), + Map.entry("shortBoxed", "int"), + Map.entry("shortArray", "int[]"), + Map.entry("shortBoxedArray", "int[]"), + Map.entry("shortBoxedList", "int[]"), + + Map.entry("int", "int"), + Map.entry("intBoxed", "int"), + Map.entry("intArray", "int[]"), + Map.entry("intBoxedArray", "int[]"), + Map.entry("intBoxedList", "int[]"), + + Map.entry("long", "int"), + Map.entry("longBoxed", "int"), + Map.entry("longArray", "int[]"), + Map.entry("longBoxedArray", "int[]"), + Map.entry("longBoxedList", "int[]"), + + Map.entry("float", "number"), + Map.entry("floatBoxed", "number"), + Map.entry("floatArray", "number[]"), + Map.entry("floatBoxedArray", "number[]"), + Map.entry("floatBoxedList", "number[]"), + + Map.entry("double", "number"), + Map.entry("doubleBoxed", "number"), + Map.entry("doubleArray", "number[]"), + Map.entry("doubleBoxedArray", "number[]"), + Map.entry("doubleBoxedList", "number[]"), + + Map.entry("boolean", "boolean"), + Map.entry("booleanBoxed", "boolean"), + Map.entry("booleanArray", "boolean[]"), + Map.entry("booleanBoxedArray", "boolean[]"), + Map.entry("booleanBoxedList", "boolean[]")); + } + + private final RecursiveComparisonConfiguration COMPARISON_CONFIG = RecursiveComparisonConfiguration.builder() + // Assertj is having a really bad time comparing List, + // so we'll just always return true here. + .withComparatorForFields((a, b) -> 0, "floatBoxedList") + .withComparatorForType((a, b) -> Double.compare(a.doubleValue(), b.doubleValue()), Number.class) + .build(); + + @Test + public void test_insertAndQuery() throws Exception { + short short_ = 666; + int int_ = 666; + long long_ = 666; + float float_ = 666; + double double_ = 666; + boolean boolean_ = true; + UUID uuid = UUID.randomUUID(); + OffsetDateTime date = OffsetDateTime.now(); + String text = "hello"; + + var thing = new Thing( + text, + new String[] { text }, + List.of(text), + + OffsetDateTime.now(), + new OffsetDateTime[] { date }, + List.of(date), + + UUID.randomUUID(), + new UUID[] { uuid }, + List.of(uuid), + + short_, + short_, + new short[] { short_ }, + new Short[] { short_ }, + List.of(short_), + + int_, + int_, + new int[] { int_ }, + new Integer[] { int_ }, + List.of(int_), + + long_, + long_, + new long[] { long_ }, + new Long[] { long_ }, + List.of(long_), + + float_, + float_, + new float[] { float_ }, + new Float[] { float_ }, + List.of(float_), + + double_, + double_, + new double[] { double_ }, + new Double[] { double_ }, + List.of(double_), + + boolean_, + boolean_, + new boolean[] { boolean_ }, + new Boolean[] { boolean_ }, + List.of(boolean_)); + + var things = client.collections.use(Thing.class); + + // Act + var inserted = things.data.insert(thing); + + // Assert + var response = things.query.byId(inserted.uuid()); + var got = Assertions.assertThat(response).get().actual(); + + Assertions.assertThat(got.properties()) + .usingRecursiveComparison(COMPARISON_CONFIG) + .isEqualTo(thing); + } + + @Test + public void test_insertManyAndQuery() throws Exception { + short short_ = 666; + int int_ = 666; + long long_ = 666; + float float_ = 666; + double double_ = 666; + boolean boolean_ = true; + UUID uuid = UUID.randomUUID(); + OffsetDateTime date = OffsetDateTime.now(); + String text = "hello"; + + var thing = new Thing( + text, + new String[] { text }, + List.of(text), + + OffsetDateTime.now(), + new OffsetDateTime[] { date }, + List.of(date), + + UUID.randomUUID(), + new UUID[] { uuid }, + List.of(uuid), + + short_, + short_, + new short[] { short_ }, + new Short[] { short_ }, + List.of(short_), + + int_, + int_, + new int[] { int_ }, + new Integer[] { int_ }, + List.of(int_), + + long_, + long_, + new long[] { long_ }, + new Long[] { long_ }, + List.of(long_), + + float_, + float_, + new float[] { float_ }, + new Float[] { float_ }, + List.of(float_), + + double_, + double_, + new double[] { double_ }, + new Double[] { double_ }, + List.of(double_), + + boolean_, + boolean_, + new boolean[] { boolean_ }, + new Boolean[] { boolean_ }, + List.of(boolean_)); + + var things = client.collections.use(Thing.class); + + // Act + var inserted = things.data.insertMany(thing, thing, thing); + + // Assert + var uuids = inserted.responses().stream().map(InsertObject::uuid).toArray(String[]::new); + var got = things.query.fetchObjects(q -> q.where(Where.uuid().containsAny(uuids))); + Assertions.assertThat(got.objects()) + .hasSize(3) + .usingRecursiveComparison(COMPARISON_CONFIG) + .asInstanceOf(InstanceOfAssertFactories.list(Thing.class)); + } +} diff --git a/src/main/java/io/weaviate/client6/v1/api/collections/WeaviateCollectionsClient.java b/src/main/java/io/weaviate/client6/v1/api/collections/WeaviateCollectionsClient.java index f71d7e9e9..2bcb3071d 100644 --- a/src/main/java/io/weaviate/client6/v1/api/collections/WeaviateCollectionsClient.java +++ b/src/main/java/io/weaviate/client6/v1/api/collections/WeaviateCollectionsClient.java @@ -25,6 +25,34 @@ public WeaviateCollectionsClient(RestTransport restTransport, GrpcTransport grpc * Obtain a handle to send requests to a particular collection. * The returned object is thread-safe. * + * @param cls Class that represents an object in the collection. + * @return a handle for a collection with {@code Class} + * properties. + */ + public CollectionHandle use(Class cls) { + return use(CollectionDescriptor.ofClass(cls), CollectionHandleDefaults.none()); + } + + /** + * Obtain a handle to send requests to a particular collection. + * The returned object is thread-safe. + * + * @param cls Class that represents an object in the collection. + * @param fn Lamda expression for optional parameters. + * @return a handle for a collection with {@code Class} + * properties. + */ + public CollectionHandle use( + Class cls, + Function> fn) { + return use(CollectionDescriptor.ofClass(cls), fn); + } + + /** + * Obtain a handle to send requests to a particular collection. + * The returned object is thread-safe. + * + * @param collectionName Name of the collection. * @return a handle for a collection with {@code Map} * properties. */ @@ -36,22 +64,79 @@ public CollectionHandle> use(String collectionName) { * Obtain a handle to send requests to a particular collection. * The returned object is thread-safe. * + * @param collectionName Name of the collection. + * @param fn Lamda expression for optional parameters. * @return a handle for a collection with {@code Map} * properties. */ public CollectionHandle> use( String collectionName, Function> fn) { - return new CollectionHandle<>( - restTransport, - grpcTransport, - CollectionDescriptor.ofMap(collectionName), - CollectionHandleDefaults.of(fn)); + return use(CollectionDescriptor.ofMap(collectionName), fn); + } + + private CollectionHandle use(CollectionDescriptor collection, + Function> fn) { + return new CollectionHandle<>(restTransport, grpcTransport, collection, CollectionHandleDefaults.of(fn)); + } + + /** + * Create a new Weaviate collection with default configuration. + * + *
{@code
+   * // Define a record class that represents an object in collection.
+   * record Song(
+   *  String title;
+   *  int yearReleased;
+   *  String[] genres;
+   * ) {}
+   *
+   * client.collections.create(Song.class);
+   * }
+ * + * @param cls Class that represents an object in the collection. + * @return the configuration of the created collection. + * @throws WeaviateApiException in case the server returned with an + * error status code. + * @throws IOException in case the request was not sent successfully + * due to a malformed request, a networking error + * or the server being unavailable. + * @see io.weaviate.client6.v1.api.collections.annotations.Collection + * @see io.weaviate.client6.v1.api.collections.annotations.Property + */ + public CollectionConfig create(Class cls) throws IOException { + var collection = CollectionDescriptor.ofClass(cls); + return create(CollectionConfig.of(collection.collectionName(), collection.configFn())); + } + + /** + * Create and configure a new Weaviate collection. See + * {@link CollectionConfig.Builder} for available options. + * + * @param cls Class that represents an object in the collection. + * @param fn Lamda expression for optional parameters. + * @return the configuration of the created collection. + * @throws WeaviateApiException in case the server returned with an + * error status code. + * @throws IOException in case the request was not sent successfully + * due to a malformed request, a networking error + * or the server being unavailable. + * @see io.weaviate.client6.v1.api.collections.annotations.Collection + * @see io.weaviate.client6.v1.api.collections.annotations.Property + * @see WeaviateCollectionsClient#create(Class) + */ + public CollectionConfig create( + Class cls, + Function> fn) throws IOException { + var collection = CollectionDescriptor.ofClass(cls); + var configFn = ObjectBuilder.partial(fn, collection.configFn()); + return create(CollectionConfig.of(collection.collectionName(), configFn)); } /** * Create a new Weaviate collection with default configuration. * + * @param collectionName Collection name. * @return the configuration of the created collection. * @throws WeaviateApiException in case the server returned with an * error status code. @@ -59,14 +144,16 @@ public CollectionHandle> use( * due to a malformed request, a networking error * or the server being unavailable. */ - public CollectionConfig create(String name) throws IOException { - return create(CollectionConfig.of(name)); + public CollectionConfig create(String collectionName) throws IOException { + return create(CollectionConfig.of(collectionName)); } /** * Create and configure a new Weaviate collection. See * {@link CollectionConfig.Builder} for available options. * + * @param collectionName Collection name. + * @param fn Lamda expression for optional parameters. * @return the configuration of the created collection. * @throws WeaviateApiException in case the server returned with an * error status code. @@ -74,9 +161,9 @@ public CollectionConfig create(String name) throws IOException { * due to a malformed request, a networking error * or the server being unavailable. */ - public CollectionConfig create(String name, + public CollectionConfig create(String collectionName, Function> fn) throws IOException { - return create(CollectionConfig.of(name, fn)); + return create(CollectionConfig.of(collectionName, fn)); } /** @@ -97,6 +184,7 @@ public CollectionConfig create(CollectionConfig collection) throws IOException { /** * Fetch Weaviate collection configuration. * + * @param collectionName Collection name. * @return the collection configuration if one with this name exists. * @throws WeaviateApiException in case the server returned with an * error status code. @@ -104,8 +192,8 @@ public CollectionConfig create(CollectionConfig collection) throws IOException { * due to a malformed request, a networking error * or the server being unavailable. */ - public Optional getConfig(String name) throws IOException { - return this.restTransport.performRequest(new GetConfigRequest(name), GetConfigRequest._ENDPOINT); + public Optional getConfig(String collectionName) throws IOException { + return this.restTransport.performRequest(new GetConfigRequest(collectionName), GetConfigRequest._ENDPOINT); } /** @@ -125,14 +213,15 @@ public List list() throws IOException { /** * Delete a Weaviate collection. * + * @param collectionName Collection name. * @throws WeaviateApiException in case the server returned with an * error status code. * @throws IOException in case the request was not sent successfully * due to a malformed request, a networking error * or the server being unavailable. */ - public void delete(String name) throws IOException { - this.restTransport.performRequest(new DeleteCollectionRequest(name), DeleteCollectionRequest._ENDPOINT); + public void delete(String collectionName) throws IOException { + this.restTransport.performRequest(new DeleteCollectionRequest(collectionName), DeleteCollectionRequest._ENDPOINT); } /** @@ -153,13 +242,14 @@ public void deleteAll() throws IOException { /** * Check if a collection with this name exists. * + * @param collectionName Collection name. * @throws WeaviateApiException in case the server returned with an * error status code. * @throws IOException in case the request was not sent successfully * due to a malformed request, a networking error * or the server being unavailable. */ - public boolean exists(String name) throws IOException { - return getConfig(name).isPresent(); + public boolean exists(String collectionName) throws IOException { + return getConfig(collectionName).isPresent(); } } diff --git a/src/main/java/io/weaviate/client6/v1/api/collections/WeaviateCollectionsClientAsync.java b/src/main/java/io/weaviate/client6/v1/api/collections/WeaviateCollectionsClientAsync.java index 4fc449cf1..e7e4755b3 100644 --- a/src/main/java/io/weaviate/client6/v1/api/collections/WeaviateCollectionsClientAsync.java +++ b/src/main/java/io/weaviate/client6/v1/api/collections/WeaviateCollectionsClientAsync.java @@ -21,46 +21,161 @@ public WeaviateCollectionsClientAsync(RestTransport restTransport, GrpcTransport this.grpcTransport = grpcTransport; } + /** + * Obtain a handle to send requests to a particular collection. + * The returned object is thread-safe. + * + * @param cls Class that represents an object in the collection. + * @return a handle for a collection with {@code Class} + * properties. + */ + public CollectionHandleAsync use(Class cls) { + return use(CollectionDescriptor.ofClass(cls), CollectionHandleDefaults.none()); + } + + /** + * Obtain a handle to send requests to a particular collection. + * The returned object is thread-safe. + * + * @param cls Class that represents an object in the collection. + * @param fn Lamda expression for optional parameters. + * @return a handle for a collection with {@code Class} + * properties. + */ + public CollectionHandleAsync use( + Class cls, + Function> fn) { + return use(CollectionDescriptor.ofClass(cls), fn); + } + + /** + * Obtain a handle to send requests to a particular collection. + * The returned object is thread-safe. + * + * @param collectionName Name of the collection. + * @return a handle for a collection with {@code Map} + * properties. + */ public CollectionHandleAsync> use(String collectionName) { return use(collectionName, CollectionHandleDefaults.none()); } + /** + * Obtain a handle to send requests to a particular collection. + * The returned object is thread-safe. + * + * @param collectionName Name of the collection. + * @param fn Lamda expression for optional parameters. + * @return a handle for a collection with {@code Map} + * properties. + */ public CollectionHandleAsync> use( String collectionName, Function> fn) { - return new CollectionHandleAsync<>( - restTransport, - grpcTransport, - CollectionDescriptor.ofMap(collectionName), - CollectionHandleDefaults.of(fn)); + return use(CollectionDescriptor.ofMap(collectionName), fn); + } + + private CollectionHandleAsync use(CollectionDescriptor collection, + Function> fn) { + return new CollectionHandleAsync<>(restTransport, grpcTransport, collection, CollectionHandleDefaults.of(fn)); + } + + /** + * Create a new Weaviate collection with default configuration. + * + *
{@code
+   * // Define a record class that represents an object in collection.
+   * record Song(
+   *  String title;
+   *  int yearReleased;
+   *  String[] genres;
+   * ) {}
+   *
+   * client.collections.create(Song.class);
+   * }
+ * + * @param cls Class that represents an object in the collection. + * @see io.weaviate.client6.v1.api.collections.annotations.Collection + * @see io.weaviate.client6.v1.api.collections.annotations.Property + */ + public CompletableFuture create(Class cls) { + var collection = CollectionDescriptor.ofClass(cls); + return create(CollectionConfig.of(collection.collectionName(), collection.configFn())); + } + + /** + * Create and configure a new Weaviate collection. See + * {@link CollectionConfig.Builder} for available options. + * + * @param cls Class that represents an object in the collection. + * @param fn Lamda expression for optional parameters. + * @see io.weaviate.client6.v1.api.collections.annotations.Collection + * @see io.weaviate.client6.v1.api.collections.annotations.Property + * @see WeaviateCollectionsClientAsync#create(Class) + */ + public CompletableFuture create(Class cls, + Function> fn) { + var collection = CollectionDescriptor.ofClass(cls); + var configFn = ObjectBuilder.partial(fn, collection.configFn()); + return create(CollectionConfig.of(collection.collectionName(), configFn)); } - public CompletableFuture create(String name) { - return create(CollectionConfig.of(name)); + /** + * Create a new Weaviate collection with default configuration. + * + * @param collectionName Collection name. + * @return the configuration of the created collection. + */ + public CompletableFuture create(String collectionName) { + return create(CollectionConfig.of(collectionName)); } - public CompletableFuture create(String name, + /** + * Create and configure a new Weaviate collection. See + * {@link CollectionConfig.Builder} for available options. + * + * @param collectionName Collection name. + * @param fn Lamda expression for optional parameters. + */ + public CompletableFuture create(String collectionName, Function> fn) { - return create(CollectionConfig.of(name, fn)); + return create(CollectionConfig.of(collectionName, fn)); } + /** + * Create a new Weaviate collection with {@link CollectionConfig}. + */ public CompletableFuture create(CollectionConfig collection) { return this.restTransport.performRequestAsync(new CreateCollectionRequest(collection), CreateCollectionRequest._ENDPOINT); } - public CompletableFuture> getConfig(String name) { - return this.restTransport.performRequestAsync(new GetConfigRequest(name), GetConfigRequest._ENDPOINT); + /** + * Fetch Weaviate collection configuration. + * + * @param collectionName Collection name. + */ + public CompletableFuture> getConfig(String collectionName) { + return this.restTransport.performRequestAsync(new GetConfigRequest(collectionName), GetConfigRequest._ENDPOINT); } public CompletableFuture> list() { return this.restTransport.performRequestAsync(new ListCollectionRequest(), ListCollectionRequest._ENDPOINT); } - public CompletableFuture delete(String name) { - return this.restTransport.performRequestAsync(new DeleteCollectionRequest(name), DeleteCollectionRequest._ENDPOINT); + /** + * Delete a Weaviate collection. + * + * @param collectionName Collection name. + */ + public CompletableFuture delete(String collectionName) { + return this.restTransport.performRequestAsync(new DeleteCollectionRequest(collectionName), + DeleteCollectionRequest._ENDPOINT); } + /** + * Delete all collections in Weaviate. + */ public CompletableFuture deleteAll() throws IOException { return list().thenCompose(collections -> { var futures = collections.stream() @@ -70,7 +185,12 @@ public CompletableFuture deleteAll() throws IOException { }); } - public CompletableFuture exists(String name) { - return getConfig(name).thenApply(Optional::isPresent); + /** + * Check if a collection with this name exists. + * + * @param collectionName Collection name. + */ + public CompletableFuture exists(String collectionName) { + return getConfig(collectionName).thenApply(Optional::isPresent); } } diff --git a/src/main/java/io/weaviate/client6/v1/api/collections/aggregate/AggregateRequest.java b/src/main/java/io/weaviate/client6/v1/api/collections/aggregate/AggregateRequest.java index 75d3046c9..89e891cf1 100644 --- a/src/main/java/io/weaviate/client6/v1/api/collections/aggregate/AggregateRequest.java +++ b/src/main/java/io/weaviate/client6/v1/api/collections/aggregate/AggregateRequest.java @@ -20,10 +20,10 @@ static Rpc { var message = WeaviateProtoAggregate.AggregateRequest.newBuilder(); - message.setCollection(collection.name()); + message.setCollection(collection.collectionName()); request.aggregation.appendTo(message); if (request.groupBy != null) { - request.groupBy.appendTo(message, collection.name()); + request.groupBy.appendTo(message, collection.collectionName()); } if (defaults.tenant() != null) { message.setTenant(defaults.tenant()); diff --git a/src/main/java/io/weaviate/client6/v1/api/collections/annotations/Collection.java b/src/main/java/io/weaviate/client6/v1/api/collections/annotations/Collection.java new file mode 100644 index 000000000..4256c95ef --- /dev/null +++ b/src/main/java/io/weaviate/client6/v1/api/collections/annotations/Collection.java @@ -0,0 +1,16 @@ +package io.weaviate.client6.v1.api.collections.annotations; + +import java.lang.annotation.ElementType; +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; +import java.lang.annotation.Target; + +@Retention(RetentionPolicy.RUNTIME) +@Target(ElementType.TYPE) +public @interface Collection { + /** The name of the collection mapped by this class. */ + String value(); + + /** Collection description to add on creation. */ + String description() default ""; +} diff --git a/src/main/java/io/weaviate/client6/v1/api/collections/annotations/Property.java b/src/main/java/io/weaviate/client6/v1/api/collections/annotations/Property.java new file mode 100644 index 000000000..89275bc1d --- /dev/null +++ b/src/main/java/io/weaviate/client6/v1/api/collections/annotations/Property.java @@ -0,0 +1,13 @@ +package io.weaviate.client6.v1.api.collections.annotations; + +import java.lang.annotation.ElementType; +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; +import java.lang.annotation.Target; + +@Retention(RetentionPolicy.RUNTIME) +@Target(ElementType.FIELD) +public @interface Property { + /** The name of the propety mapped by the record's field. */ + String value(); +} diff --git a/src/main/java/io/weaviate/client6/v1/api/collections/config/GetShardsRequest.java b/src/main/java/io/weaviate/client6/v1/api/collections/config/GetShardsRequest.java index d04ccfbcf..5237fed26 100644 --- a/src/main/java/io/weaviate/client6/v1/api/collections/config/GetShardsRequest.java +++ b/src/main/java/io/weaviate/client6/v1/api/collections/config/GetShardsRequest.java @@ -20,7 +20,7 @@ public static final Endpoint> endpoint( CollectionHandleDefaults defaults) { return SimpleEndpoint.noBody( request -> "GET", - request -> "/schema/" + collection.name() + "/shards", + request -> "/schema/" + collection.collectionName() + "/shards", request -> defaults.tenant() != null ? Map.of("tenant", defaults.tenant()) : Collections.emptyMap(), diff --git a/src/main/java/io/weaviate/client6/v1/api/collections/config/WeaviateConfigClient.java b/src/main/java/io/weaviate/client6/v1/api/collections/config/WeaviateConfigClient.java index fdc957f01..3a5ab706f 100644 --- a/src/main/java/io/weaviate/client6/v1/api/collections/config/WeaviateConfigClient.java +++ b/src/main/java/io/weaviate/client6/v1/api/collections/config/WeaviateConfigClient.java @@ -43,11 +43,11 @@ public WeaviateConfigClient(WeaviateConfigClient c, CollectionHandleDefaults def } public Optional get() throws IOException { - return collectionsClient.getConfig(collection.name()); + return collectionsClient.getConfig(collection.collectionName()); } public void addProperty(Property property) throws IOException { - this.restTransport.performRequest(new AddPropertyRequest(collection.name(), property), + this.restTransport.performRequest(new AddPropertyRequest(collection.collectionName(), property), AddPropertyRequest._ENDPOINT); } @@ -73,7 +73,7 @@ public List updateShards(ShardStatus status, String... shards) throws IOE public List updateShards(ShardStatus status, List shards) throws IOException { for (var shard : shards) { this.restTransport.performRequest( - new UpdateShardStatusRequest(collection.name(), shard, status), + new UpdateShardStatusRequest(collection.collectionName(), shard, status), UpdateShardStatusRequest._ENDPOINT); } return getShards(); diff --git a/src/main/java/io/weaviate/client6/v1/api/collections/config/WeaviateConfigClientAsync.java b/src/main/java/io/weaviate/client6/v1/api/collections/config/WeaviateConfigClientAsync.java index 34d6a66a6..b17ad26f2 100644 --- a/src/main/java/io/weaviate/client6/v1/api/collections/config/WeaviateConfigClientAsync.java +++ b/src/main/java/io/weaviate/client6/v1/api/collections/config/WeaviateConfigClientAsync.java @@ -44,11 +44,11 @@ public WeaviateConfigClientAsync(WeaviateConfigClientAsync c, CollectionHandleDe } public CompletableFuture> get() throws IOException { - return collectionsClient.getConfig(collection.name()); + return collectionsClient.getConfig(collection.collectionName()); } public CompletableFuture addProperty(Property property) throws IOException { - return this.restTransport.performRequestAsync(new AddPropertyRequest(collection.name(), property), + return this.restTransport.performRequestAsync(new AddPropertyRequest(collection.collectionName(), property), AddPropertyRequest._ENDPOINT); } @@ -76,7 +76,7 @@ public CompletableFuture> updateShards(ShardStatus status, String... public CompletableFuture> updateShards(ShardStatus status, List shards) throws IOException { var updates = shards.stream().map( shard -> this.restTransport.performRequestAsync( - new UpdateShardStatusRequest(collection.name(), shard, status), + new UpdateShardStatusRequest(collection.collectionName(), shard, status), UpdateShardStatusRequest._ENDPOINT)) .toArray(CompletableFuture[]::new); return CompletableFuture.allOf(updates).thenCompose(__ -> getShards()); diff --git a/src/main/java/io/weaviate/client6/v1/api/collections/data/DeleteManyRequest.java b/src/main/java/io/weaviate/client6/v1/api/collections/data/DeleteManyRequest.java index a472b918a..4698157ab 100644 --- a/src/main/java/io/weaviate/client6/v1/api/collections/data/DeleteManyRequest.java +++ b/src/main/java/io/weaviate/client6/v1/api/collections/data/DeleteManyRequest.java @@ -21,7 +21,7 @@ public static Rpc { var message = WeaviateProtoBatchDelete.BatchDeleteRequest.newBuilder(); - message.setCollection(collection.name()); + message.setCollection(collection.collectionName()); if (request.verbose != null) { message.setVerbose(request.verbose); diff --git a/src/main/java/io/weaviate/client6/v1/api/collections/data/DeleteObjectRequest.java b/src/main/java/io/weaviate/client6/v1/api/collections/data/DeleteObjectRequest.java index 307dfd04f..0bb205cdd 100644 --- a/src/main/java/io/weaviate/client6/v1/api/collections/data/DeleteObjectRequest.java +++ b/src/main/java/io/weaviate/client6/v1/api/collections/data/DeleteObjectRequest.java @@ -12,7 +12,7 @@ public static final Endpoint endpoint( CollectionHandleDefaults defaults) { return SimpleEndpoint.sideEffect( request -> "DELETE", - request -> "/objects/" + collection.name() + "/" + request.uuid, + request -> "/objects/" + collection.collectionName() + "/" + request.uuid, request -> defaults.queryParameters()); } } diff --git a/src/main/java/io/weaviate/client6/v1/api/collections/data/InsertManyRequest.java b/src/main/java/io/weaviate/client6/v1/api/collections/data/InsertManyRequest.java index 261a69064..c8a12fa0c 100644 --- a/src/main/java/io/weaviate/client6/v1/api/collections/data/InsertManyRequest.java +++ b/src/main/java/io/weaviate/client6/v1/api/collections/data/InsertManyRequest.java @@ -1,5 +1,6 @@ package io.weaviate.client6.v1.api.collections.data; +import java.time.OffsetDateTime; import java.util.ArrayList; import java.util.Arrays; import java.util.List; @@ -93,7 +94,7 @@ public static void buildObject(WeaviateProtoBatch.BatchObject.Builder object WeaviateObject insert, CollectionDescriptor collection, CollectionHandleDefaults defaults) { - object.setCollection(collection.name()); + object.setCollection(collection.collectionName()); var metadata = insert.metadata(); if (metadata != null) { @@ -138,10 +139,98 @@ public static void buildObject(WeaviateProtoBatch.BatchObject.Builder object if (value instanceof String v) { protoValue.setStringValue(v); + } else if (value instanceof UUID v) { + protoValue.setStringValue(v.toString()); + } else if (value instanceof OffsetDateTime v) { + protoValue.setStringValue(v.toString()); + } else if (value instanceof Boolean v) { + protoValue.setBoolValue(v.booleanValue()); } else if (value instanceof Number v) { protoValue.setNumberValue(v.doubleValue()); + } else if (value instanceof List v) { + protoValue.setListValue( + com.google.protobuf.ListValue.newBuilder() + .addAllValues(v.stream() + .map(listValue -> { + var protoListValue = com.google.protobuf.Value.newBuilder(); + if (listValue instanceof String lv) { + protoListValue.setStringValue(lv); + } else if (listValue instanceof UUID lv) { + protoListValue.setStringValue(lv.toString()); + } else if (listValue instanceof OffsetDateTime lv) { + protoListValue.setStringValue(lv.toString()); + } else if (listValue instanceof Boolean lv) { + protoListValue.setBoolValue(lv); + } else if (listValue instanceof Number lv) { + protoListValue.setNumberValue(lv.doubleValue()); + } + return protoListValue.build(); + }) + .toList())); + + } else if (value.getClass().isArray()) { + List values; + + if (value instanceof String[] v) { + values = Arrays.stream(v) + .map(lv -> com.google.protobuf.Value.newBuilder().setStringValue(lv).build()).toList(); + } else if (value instanceof UUID[] v) { + values = Arrays.stream(v) + .map(lv -> com.google.protobuf.Value.newBuilder().setStringValue(lv.toString()).build()).toList(); + } else if (value instanceof OffsetDateTime[] v) { + values = Arrays.stream(v) + .map(lv -> com.google.protobuf.Value.newBuilder().setStringValue(lv.toString()).build()).toList(); + } else if (value instanceof Boolean[] v) { + values = Arrays.stream(v) + .map(lv -> com.google.protobuf.Value.newBuilder().setBoolValue(lv).build()).toList(); + } else if (value instanceof boolean[] v) { + values = new ArrayList<>(); + for (boolean b : v) { + values.add(com.google.protobuf.Value.newBuilder().setBoolValue(b).build()); + } + } else if (value instanceof short[] v) { + values = new ArrayList<>(); + for (short s : v) { + values.add(com.google.protobuf.Value.newBuilder().setNumberValue(s).build()); + } + } else if (value instanceof int[] v) { + values = Arrays.stream(v).boxed() + .map(lv -> com.google.protobuf.Value.newBuilder().setNumberValue(lv).build()).toList(); + } else if (value instanceof long[] v) { + values = Arrays.stream(v).boxed() + .map(lv -> com.google.protobuf.Value.newBuilder().setNumberValue(lv).build()).toList(); + } else if (value instanceof float[] v) { + values = new ArrayList<>(); + for (float s : v) { + values.add(com.google.protobuf.Value.newBuilder().setNumberValue(s).build()); + } + } else if (value instanceof double[] v) { + values = Arrays.stream(v).boxed() + .map(lv -> com.google.protobuf.Value.newBuilder().setNumberValue(lv).build()).toList(); + } else if (value instanceof Short[] v) { + values = Arrays.stream(v) + .map(lv -> com.google.protobuf.Value.newBuilder().setNumberValue(lv).build()).toList(); + } else if (value instanceof Integer[] v) { + values = Arrays.stream(v) + .map(lv -> com.google.protobuf.Value.newBuilder().setNumberValue(lv).build()).toList(); + } else if (value instanceof Long[] v) { + values = Arrays.stream(v) + .map(lv -> com.google.protobuf.Value.newBuilder().setNumberValue(lv).build()).toList(); + } else if (value instanceof Float[] v) { + values = Arrays.stream(v) + .map(lv -> com.google.protobuf.Value.newBuilder().setNumberValue(lv).build()).toList(); + } else if (value instanceof Double[] v) { + values = Arrays.stream(v) + .map(lv -> com.google.protobuf.Value.newBuilder().setNumberValue(lv).build()).toList(); + } else { + throw new AssertionError("(insertMany) branch not covered " + value.getClass()); + } + + protoValue.setListValue(com.google.protobuf.ListValue.newBuilder() + .addAllValues(values) + .build()); } else { - assert false : "(insertMany) branch not covered"; + throw new AssertionError("(insertMany) branch not covered " + value.getClass()); } nonRef.putFields(entry.getKey(), protoValue.build()); diff --git a/src/main/java/io/weaviate/client6/v1/api/collections/data/ReferenceAddRequest.java b/src/main/java/io/weaviate/client6/v1/api/collections/data/ReferenceAddRequest.java index 89201f4de..21321327f 100644 --- a/src/main/java/io/weaviate/client6/v1/api/collections/data/ReferenceAddRequest.java +++ b/src/main/java/io/weaviate/client6/v1/api/collections/data/ReferenceAddRequest.java @@ -13,7 +13,8 @@ public static final Endpoint endpoint( CollectionHandleDefaults defaults) { return SimpleEndpoint.sideEffect( request -> "POST", - request -> "/objects/" + descriptor.name() + "/" + request.fromUuid + "/references/" + request.fromProperty, + request -> "/objects/" + descriptor.collectionName() + "/" + request.fromUuid + "/references/" + + request.fromProperty, request -> defaults.queryParameters(), request -> JSON.serialize(request.reference)); diff --git a/src/main/java/io/weaviate/client6/v1/api/collections/data/ReferenceDeleteRequest.java b/src/main/java/io/weaviate/client6/v1/api/collections/data/ReferenceDeleteRequest.java index fc53457ae..9144f2b2f 100644 --- a/src/main/java/io/weaviate/client6/v1/api/collections/data/ReferenceDeleteRequest.java +++ b/src/main/java/io/weaviate/client6/v1/api/collections/data/ReferenceDeleteRequest.java @@ -13,7 +13,8 @@ public static final Endpoint endpoint( CollectionHandleDefaults defaults) { return SimpleEndpoint.sideEffect( request -> "DELETE", - request -> "/objects/" + descriptor.name() + "/" + request.fromUuid + "/references/" + request.fromProperty, + request -> "/objects/" + descriptor.collectionName() + "/" + request.fromUuid + "/references/" + + request.fromProperty, request -> defaults.queryParameters(), request -> JSON.serialize(request.reference)); } diff --git a/src/main/java/io/weaviate/client6/v1/api/collections/data/ReferenceReplaceRequest.java b/src/main/java/io/weaviate/client6/v1/api/collections/data/ReferenceReplaceRequest.java index 13516d0ac..3bf7b1e30 100644 --- a/src/main/java/io/weaviate/client6/v1/api/collections/data/ReferenceReplaceRequest.java +++ b/src/main/java/io/weaviate/client6/v1/api/collections/data/ReferenceReplaceRequest.java @@ -15,7 +15,8 @@ public static final Endpoint endpoint( CollectionHandleDefaults defaults) { return SimpleEndpoint.sideEffect( request -> "PUT", - request -> "/objects/" + descriptor.name() + "/" + request.fromUuid + "/references/" + request.fromProperty, + request -> "/objects/" + descriptor.collectionName() + "/" + request.fromUuid + "/references/" + + request.fromProperty, request -> defaults.queryParameters(), request -> JSON.serialize(List.of(request.reference))); } diff --git a/src/main/java/io/weaviate/client6/v1/api/collections/data/ReplaceObjectRequest.java b/src/main/java/io/weaviate/client6/v1/api/collections/data/ReplaceObjectRequest.java index 0a786e1e2..2a3257d14 100644 --- a/src/main/java/io/weaviate/client6/v1/api/collections/data/ReplaceObjectRequest.java +++ b/src/main/java/io/weaviate/client6/v1/api/collections/data/ReplaceObjectRequest.java @@ -22,7 +22,7 @@ static final Endpoint, Void> endpoint(CollectionDesc CollectionHandleDefaults defaults) { return SimpleEndpoint.sideEffect( request -> "PUT", - request -> "/objects/" + collection.name() + "/" + request.object.metadata().uuid(), + request -> "/objects/" + collection.collectionName() + "/" + request.object.metadata().uuid(), request -> defaults.consistencyLevel() != null ? Map.of("consistency_level", defaults.consistencyLevel()) : Collections.emptyMap(), diff --git a/src/main/java/io/weaviate/client6/v1/api/collections/data/UpdateObjectRequest.java b/src/main/java/io/weaviate/client6/v1/api/collections/data/UpdateObjectRequest.java index 2d0a76d78..c174e05eb 100644 --- a/src/main/java/io/weaviate/client6/v1/api/collections/data/UpdateObjectRequest.java +++ b/src/main/java/io/weaviate/client6/v1/api/collections/data/UpdateObjectRequest.java @@ -22,7 +22,7 @@ static final Endpoint, Void> endpoint(CollectionDescr CollectionHandleDefaults defaults) { return SimpleEndpoint.sideEffect( request -> "PATCH", - request -> "/objects/" + collection.name() + "/" + request.object.metadata().uuid(), + request -> "/objects/" + collection.collectionName() + "/" + request.object.metadata().uuid(), request -> defaults.consistencyLevel() != null ? Map.of("consistency_level", defaults.consistencyLevel()) : Collections.emptyMap(), diff --git a/src/main/java/io/weaviate/client6/v1/api/collections/data/WeaviateDataClient.java b/src/main/java/io/weaviate/client6/v1/api/collections/data/WeaviateDataClient.java index 1455b8638..82cce27a6 100644 --- a/src/main/java/io/weaviate/client6/v1/api/collections/data/WeaviateDataClient.java +++ b/src/main/java/io/weaviate/client6/v1/api/collections/data/WeaviateDataClient.java @@ -46,13 +46,13 @@ public WeaviateDataClient(WeaviateDataClient c, CollectionHandleDef } public WeaviateObject insert(PropertiesT properties) throws IOException { - return insert(InsertObjectRequest.of(collection.name(), properties)); + return insert(InsertObjectRequest.of(collection.collectionName(), properties)); } public WeaviateObject insert(PropertiesT properties, Function, ObjectBuilder>> fn) throws IOException { - return insert(InsertObjectRequest.of(collection.name(), properties, fn)); + return insert(InsertObjectRequest.of(collection.collectionName(), properties, fn)); } @SafeVarargs @@ -81,14 +81,14 @@ public boolean exists(String uuid) { public void update(String uuid, Function, ObjectBuilder>> fn) throws IOException { - this.restTransport.performRequest(UpdateObjectRequest.of(collection.name(), uuid, fn), + this.restTransport.performRequest(UpdateObjectRequest.of(collection.collectionName(), uuid, fn), UpdateObjectRequest.endpoint(collection, defaults)); } public void replace(String uuid, Function, ObjectBuilder>> fn) throws IOException { - this.restTransport.performRequest(ReplaceObjectRequest.of(collection.name(), uuid, fn), + this.restTransport.performRequest(ReplaceObjectRequest.of(collection.collectionName(), uuid, fn), ReplaceObjectRequest.endpoint(collection, defaults)); } diff --git a/src/main/java/io/weaviate/client6/v1/api/collections/data/WeaviateDataClientAsync.java b/src/main/java/io/weaviate/client6/v1/api/collections/data/WeaviateDataClientAsync.java index f85696a5f..6aae3bb41 100644 --- a/src/main/java/io/weaviate/client6/v1/api/collections/data/WeaviateDataClientAsync.java +++ b/src/main/java/io/weaviate/client6/v1/api/collections/data/WeaviateDataClientAsync.java @@ -48,12 +48,12 @@ public WeaviateDataClientAsync(WeaviateDataClientAsync c, Collectio } public CompletableFuture> insert(PropertiesT properties) { - return insert(InsertObjectRequest.of(collection.name(), properties)); + return insert(InsertObjectRequest.of(collection.collectionName(), properties)); } public CompletableFuture> insert(PropertiesT properties, Function, ObjectBuilder>> fn) { - return insert(InsertObjectRequest.of(collection.name(), properties, fn)); + return insert(InsertObjectRequest.of(collection.collectionName(), properties, fn)); } public CompletableFuture> insert( @@ -82,13 +82,13 @@ public CompletableFuture exists(String uuid) { public CompletableFuture update(String uuid, Function, ObjectBuilder>> fn) { - return this.restTransport.performRequestAsync(UpdateObjectRequest.of(collection.name(), uuid, fn), + return this.restTransport.performRequestAsync(UpdateObjectRequest.of(collection.collectionName(), uuid, fn), UpdateObjectRequest.endpoint(collection, defaults)); } public CompletableFuture replace(String uuid, Function, ObjectBuilder>> fn) { - return this.restTransport.performRequestAsync(ReplaceObjectRequest.of(collection.name(), uuid, fn), + return this.restTransport.performRequestAsync(ReplaceObjectRequest.of(collection.collectionName(), uuid, fn), ReplaceObjectRequest.endpoint(collection, defaults)); } diff --git a/src/main/java/io/weaviate/client6/v1/api/collections/pagination/AsyncPaginator.java b/src/main/java/io/weaviate/client6/v1/api/collections/pagination/AsyncPaginator.java index 771b6e4b6..972f07a4f 100644 --- a/src/main/java/io/weaviate/client6/v1/api/collections/pagination/AsyncPaginator.java +++ b/src/main/java/io/weaviate/client6/v1/api/collections/pagination/AsyncPaginator.java @@ -154,7 +154,8 @@ public final Builder returnMetadata(List metadata) { return applyQueryOption(q -> q.returnMetadata(metadata)); } - private final Builder applyQueryOption(Function options) { + private final Builder applyQueryOption( + Function> options) { this.queryOptions = ObjectBuilder.partial(this.queryOptions, options); return this; } diff --git a/src/main/java/io/weaviate/client6/v1/api/collections/pagination/Paginator.java b/src/main/java/io/weaviate/client6/v1/api/collections/pagination/Paginator.java index 04b10bf11..dfc7c90ca 100644 --- a/src/main/java/io/weaviate/client6/v1/api/collections/pagination/Paginator.java +++ b/src/main/java/io/weaviate/client6/v1/api/collections/pagination/Paginator.java @@ -111,7 +111,7 @@ public final Builder returnMetadata(List metadata) { return applyQueryOption(q -> q.returnMetadata(metadata)); } - private final Builder applyQueryOption(Function options) { + private final Builder applyQueryOption(Function> options) { this.queryOptions = ObjectBuilder.partial(this.queryOptions, options); return this; } diff --git a/src/main/java/io/weaviate/client6/v1/api/collections/query/QueryRequest.java b/src/main/java/io/weaviate/client6/v1/api/collections/query/QueryRequest.java index f38ef3b26..9f42be010 100644 --- a/src/main/java/io/weaviate/client6/v1/api/collections/query/QueryRequest.java +++ b/src/main/java/io/weaviate/client6/v1/api/collections/query/QueryRequest.java @@ -34,7 +34,7 @@ static Rpc WeaviateObject unmarshalResultObjec if (metadataResult.getExplainScorePresent()) { metadata.explainScore(metadataResult.getExplainScore()); } - return new WeaviateObject<>(collection.name(), object.properties(), object.references(), metadata.build()); + return new WeaviateObject<>(collection.collectionName(), object.properties(), object.references(), + metadata.build()); } private static WeaviateObject unmarshalWithReferences( @@ -198,7 +199,7 @@ private static WeaviateObject unmarshalWithRefere } var obj = new WeaviateObject.Builder() - .collection(descriptor.name()) + .collection(descriptor.collectionName()) .properties(properties.build()) .references(referenceProperties) .metadata(metadata); diff --git a/src/main/java/io/weaviate/client6/v1/api/collections/tenants/CreateTenantsRequest.java b/src/main/java/io/weaviate/client6/v1/api/collections/tenants/CreateTenantsRequest.java index ea740e68c..aa72ede5f 100644 --- a/src/main/java/io/weaviate/client6/v1/api/collections/tenants/CreateTenantsRequest.java +++ b/src/main/java/io/weaviate/client6/v1/api/collections/tenants/CreateTenantsRequest.java @@ -12,7 +12,7 @@ public record CreateTenantsRequest(List tenants) { static Endpoint endpoint(CollectionDescriptor collection) { return SimpleEndpoint.sideEffect( __ -> "POST", - __ -> "/schema/" + collection.name() + "/tenants", + __ -> "/schema/" + collection.collectionName() + "/tenants", __ -> Collections.emptyMap(), request -> JSON.serialize(request.tenants)); } diff --git a/src/main/java/io/weaviate/client6/v1/api/collections/tenants/DeleteTenantsRequest.java b/src/main/java/io/weaviate/client6/v1/api/collections/tenants/DeleteTenantsRequest.java index 82f499314..82f956ff7 100644 --- a/src/main/java/io/weaviate/client6/v1/api/collections/tenants/DeleteTenantsRequest.java +++ b/src/main/java/io/weaviate/client6/v1/api/collections/tenants/DeleteTenantsRequest.java @@ -12,7 +12,7 @@ public record DeleteTenantsRequest(List tenants) { static Endpoint endpoint(CollectionDescriptor collection) { return SimpleEndpoint.sideEffect( __ -> "DELETE", - __ -> "/schema/" + collection.name() + "/tenants", + __ -> "/schema/" + collection.collectionName() + "/tenants", __ -> Collections.emptyMap(), request -> JSON.serialize(request.tenants)); } diff --git a/src/main/java/io/weaviate/client6/v1/api/collections/tenants/GetTenantsRequest.java b/src/main/java/io/weaviate/client6/v1/api/collections/tenants/GetTenantsRequest.java index a8095ed1b..1219c45c6 100644 --- a/src/main/java/io/weaviate/client6/v1/api/collections/tenants/GetTenantsRequest.java +++ b/src/main/java/io/weaviate/client6/v1/api/collections/tenants/GetTenantsRequest.java @@ -15,7 +15,7 @@ static final Rpc { var message = WeaviateProtoTenants.TenantsGetRequest.newBuilder() - .setCollection(collection.name()); + .setCollection(collection.collectionName()); if (!request.tenants.isEmpty()) { message.setNames(TenantNames.newBuilder() diff --git a/src/main/java/io/weaviate/client6/v1/api/collections/tenants/TenantExistsRequest.java b/src/main/java/io/weaviate/client6/v1/api/collections/tenants/TenantExistsRequest.java index 50e238457..74445af29 100644 --- a/src/main/java/io/weaviate/client6/v1/api/collections/tenants/TenantExistsRequest.java +++ b/src/main/java/io/weaviate/client6/v1/api/collections/tenants/TenantExistsRequest.java @@ -10,7 +10,7 @@ public record TenantExistsRequest(String tenant) { static Endpoint endpoint(CollectionDescriptor collection) { return new BooleanEndpoint<>( __ -> "GET", - request -> "/schema/" + collection.name() + "/tenants/" + request.tenant, + request -> "/schema/" + collection.collectionName() + "/tenants/" + request.tenant, __ -> Collections.emptyMap(), __ -> null); } diff --git a/src/main/java/io/weaviate/client6/v1/api/collections/tenants/UpdateTenantsRequest.java b/src/main/java/io/weaviate/client6/v1/api/collections/tenants/UpdateTenantsRequest.java index 37f40be00..c17334be0 100644 --- a/src/main/java/io/weaviate/client6/v1/api/collections/tenants/UpdateTenantsRequest.java +++ b/src/main/java/io/weaviate/client6/v1/api/collections/tenants/UpdateTenantsRequest.java @@ -12,7 +12,7 @@ public record UpdateTenantsRequest(List tenants) { static Endpoint endpoint(CollectionDescriptor collection) { return SimpleEndpoint.sideEffect( __ -> "PUT", - __ -> "/schema/" + collection.name() + "/tenants", + __ -> "/schema/" + collection.collectionName() + "/tenants", __ -> Collections.emptyMap(), request -> JSON.serialize(request.tenants)); } diff --git a/src/main/java/io/weaviate/client6/v1/internal/ObjectBuilder.java b/src/main/java/io/weaviate/client6/v1/internal/ObjectBuilder.java index a1ed410fc..b138326ea 100644 --- a/src/main/java/io/weaviate/client6/v1/internal/ObjectBuilder.java +++ b/src/main/java/io/weaviate/client6/v1/internal/ObjectBuilder.java @@ -28,8 +28,9 @@ static , T> Function> identity() * @param partialFn Function that will be applied first. * @return ObjectBuilder with "pre-applied" function. */ + @SuppressWarnings("unchecked") static , T> Function> partial(Function> fn, - Function partialFn) { - return partialFn.andThen(fn); + Function> partialFn) { + return partialFn.andThen(b -> fn.apply((B) b)); } } diff --git a/src/main/java/io/weaviate/client6/v1/internal/json/JSON.java b/src/main/java/io/weaviate/client6/v1/internal/json/JSON.java index a56fce609..7a157b2b9 100644 --- a/src/main/java/io/weaviate/client6/v1/internal/json/JSON.java +++ b/src/main/java/io/weaviate/client6/v1/internal/json/JSON.java @@ -4,6 +4,8 @@ import com.google.gson.GsonBuilder; import com.google.gson.reflect.TypeToken; +import io.weaviate.client6.v1.internal.orm.PropertyFieldNamingStrategy; + public final class JSON { private static final Gson gson; @@ -42,6 +44,9 @@ public final class JSON { gsonBuilder.registerTypeAdapter( io.weaviate.client6.v1.api.collections.data.ReferenceAddManyResponse.class, io.weaviate.client6.v1.api.collections.data.ReferenceAddManyResponse.CustomJsonDeserializer.INSTANCE); + + // ORM FieldNaminsStrategy ------------------------------------------------ + gsonBuilder.setFieldNamingStrategy(PropertyFieldNamingStrategy.INSTANCE); gson = gsonBuilder.create(); } diff --git a/src/main/java/io/weaviate/client6/v1/internal/orm/CollectionDescriptor.java b/src/main/java/io/weaviate/client6/v1/internal/orm/CollectionDescriptor.java index a122c7214..de7ca31a0 100644 --- a/src/main/java/io/weaviate/client6/v1/internal/orm/CollectionDescriptor.java +++ b/src/main/java/io/weaviate/client6/v1/internal/orm/CollectionDescriptor.java @@ -1,19 +1,31 @@ package io.weaviate.client6.v1.internal.orm; import java.util.Map; +import java.util.function.Function; import com.google.gson.reflect.TypeToken; -public sealed interface CollectionDescriptor permits MapDescriptor { - String name(); +import io.weaviate.client6.v1.api.collections.CollectionConfig; +import io.weaviate.client6.v1.internal.ObjectBuilder; - TypeToken typeToken(); +public sealed interface CollectionDescriptor permits MapDescriptor, PojoDescriptor { + String collectionName(); - PropertiesReader propertiesReader(T properties); + TypeToken typeToken(); - PropertiesBuilder propertiesBuilder(); + PropertiesReader propertiesReader(PropertiesT properties); + + PropertiesBuilder propertiesBuilder(); + + default Function> configFn() { + return ObjectBuilder.identity(); + } static CollectionDescriptor> ofMap(String collectionName) { return new MapDescriptor(collectionName); } + + static CollectionDescriptor ofClass(Class cls) { + return new PojoDescriptor<>(cls); + } } diff --git a/src/main/java/io/weaviate/client6/v1/internal/orm/MapDescriptor.java b/src/main/java/io/weaviate/client6/v1/internal/orm/MapDescriptor.java index 2910f2db4..1d7549a76 100644 --- a/src/main/java/io/weaviate/client6/v1/internal/orm/MapDescriptor.java +++ b/src/main/java/io/weaviate/client6/v1/internal/orm/MapDescriptor.java @@ -12,7 +12,7 @@ public MapDescriptor(String collectionName) { } @Override - public String name() { + public String collectionName() { return collectionName; } diff --git a/src/main/java/io/weaviate/client6/v1/internal/orm/PojoBuilder.java b/src/main/java/io/weaviate/client6/v1/internal/orm/PojoBuilder.java new file mode 100644 index 000000000..1fa65750c --- /dev/null +++ b/src/main/java/io/weaviate/client6/v1/internal/orm/PojoBuilder.java @@ -0,0 +1,218 @@ +package io.weaviate.client6.v1.internal.orm; + +import java.lang.reflect.Constructor; +import java.time.OffsetDateTime; +import java.util.Arrays; +import java.util.LinkedHashMap; +import java.util.List; +import java.util.Map; +import java.util.UUID; + +import org.apache.commons.lang3.ArrayUtils; + +final class PojoBuilder implements PropertiesBuilder { + private final PojoDescriptor descriptor; + private final Constructor ctor; + private final Map ctorArgs; + + static record Arg(Class type, Object value) { + Arg withValue(Object value) { + return new Arg(this.type, value); + } + } + + PojoBuilder(PojoDescriptor descriptor) { + this.descriptor = descriptor; + + var args = descriptor._class().getRecordComponents(); + ctorArgs = new LinkedHashMap(args.length); + + var componentTypes = Arrays.stream(args) + .map(arg -> { + // LinkedHashMap allows null values. + var type = arg.getType(); + ctorArgs.put(arg.getName(), new Arg(type, null)); + return type; + }) + .toArray(Class[]::new); + try { + ctor = descriptor._class().getDeclaredConstructor(componentTypes); + } catch (NoSuchMethodException | SecurityException e) { + throw new RuntimeException(e); + } + } + + private void setValue(String propertyName, Object value) { + var fieldName = descriptor.fieldName(propertyName); + if (!ctorArgs.containsKey(fieldName)) { + return; + } + var arg = ctorArgs.get(fieldName); + ctorArgs.put(fieldName, arg.withValue(value)); + } + + private Class getArgType(String propertyName) { + return ctorArgs.get(descriptor.fieldName(propertyName)).type(); + } + + private boolean isArray(String propertyName, Class... classes) { + var type = getArgType(propertyName); + if (!type.isArray()) { + return false; + } + if (classes.length == 0) { + return true; + } + var componentType = type.getComponentType(); + for (final var cls : classes) { + if (componentType == cls) { + return true; + } + } + return false; + } + + /** Is either of types. */ + private boolean isType(String propertyName, Class... classes) { + var type = getArgType(propertyName); + for (final var cls : classes) { + if (type == cls) { + return true; + } + } + return false; + } + + @Override + public void setNull(String propertyName) { + setValue(propertyName, null); + } + + @Override + public void setText(String propertyName, String value) { + setValue(propertyName, value); + } + + @Override + public void setBoolean(String propertyName, Boolean value) { + if (isType(propertyName, boolean.class)) { + setValue(propertyName, value.booleanValue()); + } else { + setValue(propertyName, value); + } + } + + @Override + // TODO: rename to setLong + public void setInteger(String propertyName, Long value) { + if (isType(propertyName, short.class, Short.class)) { + setValue(propertyName, value.shortValue()); + } else if (isType(propertyName, int.class, Integer.class)) { + setValue(propertyName, value.intValue()); + } else { + setValue(propertyName, value); + } + } + + @Override + public void setDouble(String propertyName, Double value) { + if (isType(propertyName, float.class, Float.class)) { + setValue(propertyName, value.floatValue()); + } else { + setValue(propertyName, value); + } + } + + @Override + public void setBlob(String propertyName, String value) { + setValue(propertyName, value); + } + + @Override + public void setOffsetDateTime(String propertyName, OffsetDateTime value) { + setValue(propertyName, value); + } + + @Override + public void setUuid(String propertyName, UUID value) { + setValue(propertyName, value); + } + + @Override + public void setTextArray(String propertyName, List value) { + setValue(propertyName, isArray(propertyName) + ? value.toArray(String[]::new) + : value); + + } + + @Override + public void setLongArray(String propertyName, List value) { + if (isArray(propertyName, short.class)) { + setValue(propertyName, ArrayUtils.toPrimitive(value.stream().map(Long::shortValue).toArray(Short[]::new))); + } else if (isArray(propertyName, Short.class)) { + setValue(propertyName, value.stream().map(Long::shortValue).toArray(Short[]::new)); + } else if (isArray(propertyName, int.class)) { + setValue(propertyName, ArrayUtils.toPrimitive(value.stream().map(Long::intValue).toArray(Integer[]::new))); + } else if (isArray(propertyName, Integer.class)) { + setValue(propertyName, value.stream().map(Long::intValue).toArray(Integer[]::new)); + } else if (isArray(propertyName, long.class)) { + setValue(propertyName, ArrayUtils.toPrimitive(value.stream().map(Long::longValue).toArray(Long[]::new))); + } else if (isArray(propertyName, Long.class)) { + setValue(propertyName, value.stream().toArray(Long[]::new)); + } else { + setValue(propertyName, value); + } + } + + @Override + public void setDoubleArray(String propertyName, List value) { + if (isArray(propertyName, float.class)) { + setValue(propertyName, ArrayUtils.toPrimitive(value.stream().map(Double::floatValue).toArray(Float[]::new))); + } else if (isArray(propertyName, Float.class)) { + setValue(propertyName, value.stream().map(Double::floatValue).toArray(Float[]::new)); + } else if (isArray(propertyName, double.class)) { + setValue(propertyName, ArrayUtils.toPrimitive(value.stream().map(Double::doubleValue).toArray(Double[]::new))); + } else if (isArray(propertyName, Double.class)) { + setValue(propertyName, value.stream().toArray(Double[]::new)); + } else { + setValue(propertyName, value); + } + } + + @Override + public void setUuidArray(String propertyName, List value) { + setValue(propertyName, isArray(propertyName) + ? value.toArray(UUID[]::new) + : value); + } + + @Override + public void setBooleanArray(String propertyName, List value) { + if (isArray(propertyName, boolean.class)) { + setValue(propertyName, ArrayUtils.toPrimitive(value.stream().map(Boolean::booleanValue).toArray(Boolean[]::new))); + } else if (isArray(propertyName, Boolean.class)) { + setValue(propertyName, value.stream().map(Boolean::booleanValue).toArray(Boolean[]::new)); + } else { + setValue(propertyName, value); + } + } + + @Override + public void setOffsetDateTimeArray(String propertyName, List value) { + setValue(propertyName, isArray(propertyName) + ? value.toArray(OffsetDateTime[]::new) + : value); + } + + @Override + public PropertiesT build() { + Object[] args = ctorArgs.values().stream().map(Arg::value).toArray(); + try { + ctor.setAccessible(true); + return ctor.newInstance(args); + } catch (Exception e) { + throw new RuntimeException(e); + } + } +} diff --git a/src/main/java/io/weaviate/client6/v1/internal/orm/PojoDescriptor.java b/src/main/java/io/weaviate/client6/v1/internal/orm/PojoDescriptor.java new file mode 100644 index 000000000..bcb1b4703 --- /dev/null +++ b/src/main/java/io/weaviate/client6/v1/internal/orm/PojoDescriptor.java @@ -0,0 +1,159 @@ +package io.weaviate.client6.v1.internal.orm; + +import java.lang.reflect.Array; +import java.lang.reflect.Field; +import java.lang.reflect.ParameterizedType; +import java.time.OffsetDateTime; +import java.util.Arrays; +import java.util.Collections; +import java.util.HashMap; +import java.util.List; +import java.util.Map; +import java.util.UUID; +import java.util.function.Function; +import java.util.stream.Collectors; + +import com.google.gson.reflect.TypeToken; + +import io.weaviate.client6.v1.api.collections.CollectionConfig; +import io.weaviate.client6.v1.api.collections.Property; +import io.weaviate.client6.v1.api.collections.annotations.Collection; +import io.weaviate.client6.v1.internal.ObjectBuilder; + +final class PojoDescriptor implements CollectionDescriptor { + private static final Map, Function> CTORS; + + static { + Map, Function> ctors = new HashMap<>() { + { + put(String.class, Property::text); + put(String[].class, Property::textArray); + + put(OffsetDateTime.class, Property::date); + put(OffsetDateTime[].class, Property::dateArray); + + put(UUID.class, Property::uuid); + put(UUID[].class, Property::uuidArray); + + put(boolean.class, Property::bool); + put(Boolean.class, Property::bool); + put(boolean[].class, Property::boolArray); + put(Boolean[].class, Property::boolArray); + + put(short.class, Property::integer); + put(Short.class, Property::integer); + put(int.class, Property::integer); + put(Integer.class, Property::integer); + put(long.class, Property::integer); + put(Long.class, Property::integer); + + put(short[].class, Property::integerArray); + put(Short[].class, Property::integerArray); + put(int[].class, Property::integerArray); + put(Integer[].class, Property::integerArray); + put(long[].class, Property::integerArray); + put(Long[].class, Property::integerArray); + + put(float.class, Property::number); + put(Float.class, Property::number); + put(double.class, Property::number); + put(Double.class, Property::number); + + put(float[].class, Property::numberArray); + put(Float[].class, Property::numberArray); + put(double[].class, Property::numberArray); + put(Double[].class, Property::numberArray); + } + }; + CTORS = Collections.unmodifiableMap(ctors); + } + + private final Class cls; + private final Map propertyToField; + + PojoDescriptor(Class cls) { + this.cls = cls; + this.propertyToField = Arrays.stream(cls.getDeclaredFields()) + .collect(Collectors.toUnmodifiableMap(PojoDescriptor::propertyName, Field::getName)); + } + + Class _class() { + return cls; + } + + /** Get collection property name for a class field. */ + static String propertyName(Field field) { + var annotation = field.getAnnotation(io.weaviate.client6.v1.api.collections.annotations.Property.class); + var propertyName = field.getName(); + if (annotation != null) { + propertyName = annotation.value(); + } + return propertyName; + } + + /** Get class field name for a collection property. */ + String fieldName(String propertyName) { + return propertyToField.getOrDefault(propertyName, propertyName); + } + + @Override + public String collectionName() { + var annotation = cls.getAnnotation(Collection.class); + if (annotation != null) { + return annotation.value(); + } + return cls.getSimpleName(); + } + + @Override + public TypeToken typeToken() { + return TypeToken.get(cls); + } + + @Override + public PropertiesReader propertiesReader(T properties) { + return new PojoReader<>(properties); + } + + @Override + public PropertiesBuilder propertiesBuilder() { + return new PojoBuilder<>(this); + } + + @Override + public Function> configFn() { + return this::inspectClass; + } + + private ObjectBuilder inspectClass(CollectionConfig.Builder b) { + var annotation = cls.getAnnotation(Collection.class); + if (annotation != null) { + b.description(annotation.description()); + } + + // Add properties + for (var field : cls.getDeclaredFields()) { + var propertyName = propertyName(field); + Function ctor; + var type = field.getType(); + + if (type == List.class) { + var ptype = (ParameterizedType) field.getGenericType(); + var argtype = (Class) ptype.getActualTypeArguments()[0]; + var arr = Array.newInstance(argtype, 0).getClass(); + ctor = CTORS.get(arr); + } else { + ctor = CTORS.get(type); + } + + if (ctor == null) { + throw new IllegalArgumentException(type.getCanonicalName() + " fields are not supported"); + } + + assert ctor != null; + b.properties(ctor.apply(propertyName)); + } + return b; + } + +} diff --git a/src/main/java/io/weaviate/client6/v1/internal/orm/PojoReader.java b/src/main/java/io/weaviate/client6/v1/internal/orm/PojoReader.java new file mode 100644 index 000000000..82ccea5a3 --- /dev/null +++ b/src/main/java/io/weaviate/client6/v1/internal/orm/PojoReader.java @@ -0,0 +1,27 @@ +package io.weaviate.client6.v1.internal.orm; + +import java.util.HashMap; +import java.util.Map; + +final class PojoReader implements PropertiesReader { + private final PropertiesT properties; + + PojoReader(PropertiesT properties) { + this.properties = properties; + } + + @Override + public Map readProperties() { + var out = new HashMap(); + for (var field : properties.getClass().getDeclaredFields()) { + var propertyName = PojoDescriptor.propertyName(field); + field.setAccessible(true); + try { + out.put(propertyName, field.get(properties)); + } catch (IllegalAccessException e) { + assert e == null : e.getMessage(); + } + } + return out; + } +} diff --git a/src/main/java/io/weaviate/client6/v1/internal/orm/PropertyFieldNamingStrategy.java b/src/main/java/io/weaviate/client6/v1/internal/orm/PropertyFieldNamingStrategy.java new file mode 100644 index 000000000..0d6bdd80e --- /dev/null +++ b/src/main/java/io/weaviate/client6/v1/internal/orm/PropertyFieldNamingStrategy.java @@ -0,0 +1,14 @@ +package io.weaviate.client6.v1.internal.orm; + +import java.lang.reflect.Field; + +import com.google.gson.FieldNamingStrategy; + +public enum PropertyFieldNamingStrategy implements FieldNamingStrategy { + INSTANCE; + + @Override + public String translateName(Field field) { + return PojoDescriptor.propertyName(field); + } +}