diff --git a/src/it/java/io/weaviate/integration/DataITest.java b/src/it/java/io/weaviate/integration/DataITest.java index 9bc4b7fbc..0c701985e 100644 --- a/src/it/java/io/weaviate/integration/DataITest.java +++ b/src/it/java/io/weaviate/integration/DataITest.java @@ -15,6 +15,7 @@ import io.weaviate.client6.v1.api.collections.WeaviateObject; import io.weaviate.client6.v1.api.collections.data.Reference; 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.QueryReference; import io.weaviate.client6.v1.api.collections.vectorindex.Hnsw; import io.weaviate.client6.v1.api.collections.vectorizers.NoneVectorizer; @@ -45,8 +46,10 @@ public void testCreateGetDelete() throws IOException { .returnProperties("name") .returnMetadata(Metadata.ID, Metadata.VECTOR)); + Assertions.assertThat(artists.data.exists(id)) + .as("object exists after insert").isTrue(); Assertions.assertThat(object) - .as("object exists after insert").get() + .as("object has correct properties").get() .satisfies(obj -> { Assertions.assertThat(obj.metadata().uuid()) .as("object id").isEqualTo(id); @@ -60,8 +63,8 @@ public void testCreateGetDelete() throws IOException { }); artists.data.delete(id); - object = artists.query.byId(id); - Assertions.assertThat(object).isEmpty().as("object not exists after deletion"); + Assertions.assertThat(artists.data.exists(id)) + .as("object not exists after deletion").isFalse(); } @Test @@ -175,4 +178,97 @@ public void testReferences_AddReplaceDelete() throws IOException { .asInstanceOf(InstanceOfAssertFactories.list(WeaviateObject.class)) .isEmpty(); } + + @Test + public void testReplace() throws IOException { + // Arrange + var nsBooks = ns("Books"); + + client.collections.create(nsBooks, + collection -> collection + .properties(Property.text("title"), Property.integer("year"))); + + // Add 1 book with 'title' only. + var books = client.collections.use(nsBooks); + var ivanhoe = books.data.insert(Map.of("title", "ivanhoe")); + + // Act + books.data.replace(ivanhoe.metadata().uuid(), + replace -> replace.properties(Map.of("year", 1819))); + + // Assert + var replacedIvanhoe = books.query.byId(ivanhoe.metadata().uuid()); + + Assertions.assertThat(replacedIvanhoe).get() + .as("has ONLY year property") + .extracting(WeaviateObject::properties, InstanceOfAssertFactories.MAP) + .doesNotContain(Map.entry("title", "ivanhoe")) + .contains(Map.entry("year", 1819L)); + } + + @Test + public void testUpdate() throws IOException { + // Arrange + var nsBooks = ns("Books"); + var nsAuthors = ns("Authors"); + + client.collections.create(nsAuthors, + collection -> collection + .properties(Property.text("name"))); + + client.collections.create(nsBooks, + collection -> collection + .properties(Property.text("title"), Property.integer("year")) + .references(Property.reference("writtenBy", nsAuthors)) + .vector(Hnsw.of(new NoneVectorizer()))); + + var authors = client.collections.use(nsAuthors); + var walter = authors.data.insert(Map.of("name", "walter scott")); + + var vector = new Float[] { 1f, 2f, 3f }; + + var books = client.collections.use(nsBooks); + + // Add 1 book without mentioning its author, year published, + // or supplying a vector. + var ivanhoe = books.data.insert(Map.of("title", "ivanhoe")); + + // Act + books.data.update(ivanhoe.metadata().uuid(), + update -> update + .properties(Map.of("year", 1819)) + .reference("writtenBy", Reference.objects(walter)) + .vectors(Vectors.of(vector))); + + // Assert + var updIvanhoe = books.query.byId( + ivanhoe.metadata().uuid(), + query -> query + .returnMetadata(Metadata.VECTOR) + .returnReferences( + QueryReference.single("writtenBy", + writtenBy -> writtenBy.returnMetadata(Metadata.ID)))); + + Assertions.assertThat(updIvanhoe).get() + .satisfies(book -> { + Assertions.assertThat(book) + .as("has both year and title property") + .extracting(WeaviateObject::properties, InstanceOfAssertFactories.MAP) + .contains(Map.entry("title", "ivanhoe"), Map.entry("year", 1819L)); + + Assertions.assertThat(book) + .as("has reference to Authors") + .extracting(WeaviateObject::references, InstanceOfAssertFactories.MAP) + .extractingByKey("writtenBy", InstanceOfAssertFactories.list(WeaviateObject.class)) + .first() + .extracting(WeaviateObject::properties, InstanceOfAssertFactories.MAP) + .contains(Map.entry("name", "walter scott")); + + Assertions.assertThat(book) + .as("has a vector") + .extracting(WeaviateObject::metadata) + .extracting(QueryMetadata::vectors) + .returns(vector, Vectors::getDefaultSingle); + }); + } } diff --git a/src/main/java/io/weaviate/client6/v1/api/collections/CollectionHandle.java b/src/main/java/io/weaviate/client6/v1/api/collections/CollectionHandle.java index 47f569cc7..2dd4529bd 100644 --- a/src/main/java/io/weaviate/client6/v1/api/collections/CollectionHandle.java +++ b/src/main/java/io/weaviate/client6/v1/api/collections/CollectionHandle.java @@ -20,8 +20,8 @@ public CollectionHandle( CollectionDescriptor collectionDescriptor) { this.config = new WeaviateConfigClient(collectionDescriptor, restTransport, grpcTransport); - this.data = new WeaviateDataClient<>(collectionDescriptor, restTransport); this.query = new WeaviateQueryClient<>(collectionDescriptor, grpcTransport); + this.data = new WeaviateDataClient<>(collectionDescriptor, restTransport, this.query); this.aggregate = new WeaviateAggregateClient(collectionDescriptor, grpcTransport); } } diff --git a/src/main/java/io/weaviate/client6/v1/api/collections/CollectionHandleAsync.java b/src/main/java/io/weaviate/client6/v1/api/collections/CollectionHandleAsync.java index 95a3096c6..ec41a5a21 100644 --- a/src/main/java/io/weaviate/client6/v1/api/collections/CollectionHandleAsync.java +++ b/src/main/java/io/weaviate/client6/v1/api/collections/CollectionHandleAsync.java @@ -20,8 +20,8 @@ public CollectionHandleAsync( CollectionDescriptor collectionDescriptor) { this.config = new WeaviateConfigClientAsync(collectionDescriptor, restTransport, grpcTransport); - this.data = new WeaviateDataClientAsync<>(collectionDescriptor, restTransport); this.query = new WeaviateQueryClientAsync<>(collectionDescriptor, grpcTransport); + this.data = new WeaviateDataClientAsync<>(collectionDescriptor, restTransport, this.query); this.aggregate = new WeaviateAggregateClientAsync(collectionDescriptor, grpcTransport); } } 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 new file mode 100644 index 000000000..1da3de392 --- /dev/null +++ b/src/main/java/io/weaviate/client6/v1/api/collections/data/ReplaceObjectRequest.java @@ -0,0 +1,68 @@ +package io.weaviate.client6.v1.api.collections.data; + +import java.util.Collections; +import java.util.function.Function; + +import com.google.gson.reflect.TypeToken; + +import io.weaviate.client6.v1.api.collections.ObjectMetadata; +import io.weaviate.client6.v1.api.collections.Vectors; +import io.weaviate.client6.v1.api.collections.WeaviateObject; +import io.weaviate.client6.v1.internal.ObjectBuilder; +import io.weaviate.client6.v1.internal.json.JSON; +import io.weaviate.client6.v1.internal.orm.CollectionDescriptor; +import io.weaviate.client6.v1.internal.rest.Endpoint; + +public record ReplaceObjectRequest(WeaviateObject object) { + + static final Endpoint, Void> endpoint(CollectionDescriptor collectionDescriptor) { + return Endpoint.of( + request -> "PUT", + request -> "/objects/" + collectionDescriptor.name() + "/" + request.object.metadata().uuid(), + (gson, request) -> JSON.serialize(request.object, TypeToken.getParameterized( + WeaviateObject.class, collectionDescriptor.typeToken().getType(), Reference.class, ObjectMetadata.class)), + request -> Collections.emptyMap(), + code -> code != 200, + (gson, response) -> null); + } + + public static ReplaceObjectRequest of(String collectionName, String uuid, + Function, ObjectBuilder>> fn) { + return fn.apply(new Builder<>(collectionName, uuid)).build(); + } + + public ReplaceObjectRequest(Builder builder) { + this(builder.object.build()); + } + + public static class Builder implements ObjectBuilder> { + private final WeaviateObject.Builder object = new WeaviateObject.Builder<>(); + private final ObjectMetadata.Builder metadata = new ObjectMetadata.Builder(); + + public Builder(String collectionName, String uuid) { + this.object.collection(collectionName); + this.metadata.uuid(uuid); + } + + public Builder properties(T properties) { + this.object.properties(properties); + return this; + } + + public Builder vectors(Vectors vectors) { + this.metadata.vectors(vectors); + return this; + } + + public Builder reference(String property, Reference... references) { + this.object.reference(property, references); + return this; + } + + @Override + public ReplaceObjectRequest build() { + this.object.metadata(this.metadata.build()); + return new ReplaceObjectRequest<>(this); + } + } +} 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 new file mode 100644 index 000000000..d9ccc0a09 --- /dev/null +++ b/src/main/java/io/weaviate/client6/v1/api/collections/data/UpdateObjectRequest.java @@ -0,0 +1,68 @@ +package io.weaviate.client6.v1.api.collections.data; + +import java.util.Collections; +import java.util.function.Function; + +import com.google.gson.reflect.TypeToken; + +import io.weaviate.client6.v1.api.collections.ObjectMetadata; +import io.weaviate.client6.v1.api.collections.Vectors; +import io.weaviate.client6.v1.api.collections.WeaviateObject; +import io.weaviate.client6.v1.internal.ObjectBuilder; +import io.weaviate.client6.v1.internal.json.JSON; +import io.weaviate.client6.v1.internal.orm.CollectionDescriptor; +import io.weaviate.client6.v1.internal.rest.Endpoint; + +public record UpdateObjectRequest(WeaviateObject object) { + + static final Endpoint, Void> endpoint(CollectionDescriptor collectionDescriptor) { + return Endpoint.of( + request -> "PATCH", + request -> "/objects/" + collectionDescriptor.name() + "/" + request.object.metadata().uuid(), + (gson, request) -> JSON.serialize(request.object, TypeToken.getParameterized( + WeaviateObject.class, collectionDescriptor.typeToken().getType(), Reference.class, ObjectMetadata.class)), + request -> Collections.emptyMap(), + code -> code != 204, + (gson, response) -> null); + } + + public static UpdateObjectRequest of(String collectionName, String uuid, + Function, ObjectBuilder>> fn) { + return fn.apply(new Builder<>(collectionName, uuid)).build(); + } + + public UpdateObjectRequest(Builder builder) { + this(builder.object.build()); + } + + public static class Builder implements ObjectBuilder> { + private final WeaviateObject.Builder object = new WeaviateObject.Builder<>(); + private final ObjectMetadata.Builder metadata = new ObjectMetadata.Builder(); + + public Builder(String collectionName, String uuid) { + this.object.collection(collectionName); + this.metadata.uuid(uuid); + } + + public Builder properties(T properties) { + this.object.properties(properties); + return this; + } + + public Builder vectors(Vectors vectors) { + this.metadata.vectors(vectors); + return this; + } + + public Builder reference(String property, Reference... references) { + this.object.reference(property, references); + return this; + } + + @Override + public UpdateObjectRequest build() { + this.object.metadata(this.metadata.build()); + return new UpdateObjectRequest<>(this); + } + } +} 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 e08cd0ee6..9228d7ec8 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 @@ -5,6 +5,7 @@ import io.weaviate.client6.v1.api.collections.ObjectMetadata; import io.weaviate.client6.v1.api.collections.WeaviateObject; +import io.weaviate.client6.v1.api.collections.query.WeaviateQueryClient; import io.weaviate.client6.v1.internal.ObjectBuilder; import io.weaviate.client6.v1.internal.orm.CollectionDescriptor; import io.weaviate.client6.v1.internal.rest.RestTransport; @@ -13,9 +14,13 @@ public class WeaviateDataClient { private final RestTransport restTransport; private final CollectionDescriptor collectionDescriptor; - public WeaviateDataClient(CollectionDescriptor collectionDescriptor, RestTransport restTransport) { + private final WeaviateQueryClient query; + + public WeaviateDataClient(CollectionDescriptor collectionDescriptor, RestTransport restTransport, + WeaviateQueryClient query) { this.restTransport = restTransport; this.collectionDescriptor = collectionDescriptor; + this.query = query; } public WeaviateObject insert(T properties) throws IOException { @@ -32,6 +37,22 @@ public WeaviateObject insert(InsertObjectRequest r return this.restTransport.performRequest(request, InsertObjectRequest.endpoint(collectionDescriptor)); } + public boolean exists(String uuid) throws IOException { + return this.query.byId(uuid).isPresent(); + } + + public void update(String uuid, Function, ObjectBuilder>> fn) + throws IOException { + this.restTransport.performRequest(UpdateObjectRequest.of(collectionDescriptor.name(), uuid, fn), + UpdateObjectRequest.endpoint(collectionDescriptor)); + } + + public void replace(String uuid, Function, ObjectBuilder>> fn) + throws IOException { + this.restTransport.performRequest(ReplaceObjectRequest.of(collectionDescriptor.name(), uuid, fn), + ReplaceObjectRequest.endpoint(collectionDescriptor)); + } + public void delete(String uuid) throws IOException { this.restTransport.performRequest(new DeleteObjectRequest(collectionDescriptor.name(), uuid), DeleteObjectRequest._ENDPOINT); 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 c73141e79..8f43ca293 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 @@ -2,11 +2,13 @@ import java.io.IOException; import java.util.Collection; +import java.util.Optional; import java.util.concurrent.CompletableFuture; import java.util.function.Function; import io.weaviate.client6.v1.api.collections.ObjectMetadata; import io.weaviate.client6.v1.api.collections.WeaviateObject; +import io.weaviate.client6.v1.api.collections.query.WeaviateQueryClientAsync; import io.weaviate.client6.v1.internal.ObjectBuilder; import io.weaviate.client6.v1.internal.orm.CollectionDescriptor; import io.weaviate.client6.v1.internal.rest.RestTransport; @@ -15,9 +17,13 @@ public class WeaviateDataClientAsync { private final RestTransport restTransport; private final CollectionDescriptor collectionDescriptor; - public WeaviateDataClientAsync(CollectionDescriptor collectionDescriptor, RestTransport restTransport) { + private final WeaviateQueryClientAsync query; + + public WeaviateDataClientAsync(CollectionDescriptor collectionDescriptor, RestTransport restTransport, + WeaviateQueryClientAsync query) { this.restTransport = restTransport; this.collectionDescriptor = collectionDescriptor; + this.query = query; } public CompletableFuture> insert(T properties) throws IOException { @@ -35,6 +41,24 @@ public CompletableFuture> insert(Inser return this.restTransport.performRequestAsync(request, InsertObjectRequest.endpoint(collectionDescriptor)); } + public CompletableFuture exists(String uuid) { + return this.query.byId(uuid).thenApply(Optional::isPresent); + } + + public CompletableFuture update(String uuid, + Function, ObjectBuilder>> fn) + throws IOException { + return this.restTransport.performRequestAsync(UpdateObjectRequest.of(collectionDescriptor.name(), uuid, fn), + UpdateObjectRequest.endpoint(collectionDescriptor)); + } + + public CompletableFuture replace(String uuid, + Function, ObjectBuilder>> fn) + throws IOException { + return this.restTransport.performRequestAsync(ReplaceObjectRequest.of(collectionDescriptor.name(), uuid, fn), + ReplaceObjectRequest.endpoint(collectionDescriptor)); + } + public CompletableFuture delete(String uuid) { return this.restTransport.performRequestAsync(new DeleteObjectRequest(collectionDescriptor.name(), uuid), DeleteObjectRequest._ENDPOINT);