diff --git a/src/it/java/io/weaviate/integration/AliasITest.java b/src/it/java/io/weaviate/integration/AliasITest.java new file mode 100644 index 00000000..f5b1dd47 --- /dev/null +++ b/src/it/java/io/weaviate/integration/AliasITest.java @@ -0,0 +1,59 @@ +package io.weaviate.integration; + +import java.io.IOException; +import java.util.List; + +import org.assertj.core.api.Assertions; +import org.junit.Test; + +import io.weaviate.ConcurrentTest; +import io.weaviate.client6.v1.api.WeaviateClient; +import io.weaviate.client6.v1.api.alias.Alias; +import io.weaviate.containers.Container; + +public class AliasITest extends ConcurrentTest { + private static WeaviateClient client = Container.WEAVIATE.getClient(); + + @Test + public void test_aliasLifecycle() throws IOException { + // Arrange + var nsPaulHewson = ns("PaulHewson"); + var nsGeorgeBarnes = ns("GeorgeBarnes"); + var nsColsonBaker = ns("ColsonBaker"); + + for (var collection : List.of(nsPaulHewson, nsGeorgeBarnes, nsColsonBaker)) { + client.collections.create(collection); + } + + // Act: create aliases + client.alias.create(nsPaulHewson, "Bono"); + client.alias.create(nsGeorgeBarnes, "MachineGunKelly"); + + // Assert: list all + var aliases = client.alias.list(); + Assertions.assertThat(aliases).hasSize(2); + Assertions.assertThat(aliases) + .as("created Bono and MachineGunKelly aliases") + .contains( + new Alias(nsPaulHewson, "Bono"), + new Alias(nsGeorgeBarnes, "MachineGunKelly")); + + // Act: update aliases + client.alias.update("MachineGunKelly", nsColsonBaker); + + // Assert: check MGK points to another collection + var mgk = client.alias.get("MachineGunKelly"); + Assertions.assertThat(mgk).get() + .as("updated MachineGunKelly alias") + .returns(nsColsonBaker, Alias::collection); + + // Act: delete Bono alias + client.alias.delete("Bono"); + + // Assert + var paulHewsonAliases = client.alias.list(all -> all.collection(nsPaulHewson)); + Assertions.assertThat(paulHewsonAliases) + .as("no aliases once Bono is deleted") + .isEmpty(); + } +} diff --git a/src/main/java/io/weaviate/client6/v1/api/WeaviateClient.java b/src/main/java/io/weaviate/client6/v1/api/WeaviateClient.java index 956137ac..970bbcfd 100644 --- a/src/main/java/io/weaviate/client6/v1/api/WeaviateClient.java +++ b/src/main/java/io/weaviate/client6/v1/api/WeaviateClient.java @@ -3,6 +3,7 @@ import java.io.IOException; import java.util.function.Function; +import io.weaviate.client6.v1.api.alias.WeaviateAliasClient; import io.weaviate.client6.v1.api.collections.WeaviateCollectionsClient; import io.weaviate.client6.v1.internal.ObjectBuilder; import io.weaviate.client6.v1.internal.TokenProvider; @@ -20,8 +21,16 @@ public class WeaviateClient implements AutoCloseable { private final RestTransport restTransport; private final GrpcTransport grpcTransport; + /** + * Client for {@code /schema} endpoints for managing Weaviate collections. + * See {@link WeaviateCollectionsClient#use} for populating and querying + * collections. + */ public final WeaviateCollectionsClient collections; + /** Client for {@code /aliases} endpoints for managing collection aliases. */ + public final WeaviateAliasClient alias; + public WeaviateClient(Config config) { this.config = config; @@ -46,6 +55,7 @@ public WeaviateClient(Config config) { this.restTransport = new DefaultRestTransport(restOpt); this.grpcTransport = new DefaultGrpcTransport(grpcOpt); + this.alias = new WeaviateAliasClient(restTransport); this.collections = new WeaviateCollectionsClient(restTransport, grpcTransport); } diff --git a/src/main/java/io/weaviate/client6/v1/api/WeaviateClientAsync.java b/src/main/java/io/weaviate/client6/v1/api/WeaviateClientAsync.java index de926288..2af8870e 100644 --- a/src/main/java/io/weaviate/client6/v1/api/WeaviateClientAsync.java +++ b/src/main/java/io/weaviate/client6/v1/api/WeaviateClientAsync.java @@ -4,6 +4,8 @@ import java.util.concurrent.CompletableFuture; import java.util.function.Function; +import io.weaviate.client6.v1.api.alias.WeaviateAliasClientAsync; +import io.weaviate.client6.v1.api.collections.WeaviateCollectionsClient; import io.weaviate.client6.v1.api.collections.WeaviateCollectionsClientAsync; import io.weaviate.client6.v1.internal.ObjectBuilder; import io.weaviate.client6.v1.internal.TokenProvider; @@ -18,8 +20,16 @@ public class WeaviateClientAsync implements AutoCloseable { private final RestTransport restTransport; private final GrpcTransport grpcTransport; + /** + * Client for {@code /schema} endpoints for managing Weaviate collections. + * See {@link WeaviateCollectionsClient#use} for populating and querying + * collections. + */ public final WeaviateCollectionsClientAsync collections; + /** Client for {@code /aliases} endpoints for managing collection aliases. */ + public final WeaviateAliasClientAsync alias; + /** * This constructor is blocking if {@link Authentication} configured, * as the client will need to do the initial token exchange. @@ -46,6 +56,7 @@ public WeaviateClientAsync(Config config) { this.restTransport = new DefaultRestTransport(restOpt); this.grpcTransport = new DefaultGrpcTransport(grpcOpt); + this.alias = new WeaviateAliasClientAsync(restTransport); this.collections = new WeaviateCollectionsClientAsync(restTransport, grpcTransport); } diff --git a/src/main/java/io/weaviate/client6/v1/api/alias/Alias.java b/src/main/java/io/weaviate/client6/v1/api/alias/Alias.java new file mode 100644 index 00000000..d19c6821 --- /dev/null +++ b/src/main/java/io/weaviate/client6/v1/api/alias/Alias.java @@ -0,0 +1,10 @@ +package io.weaviate.client6.v1.api.alias; + +import com.google.gson.annotations.SerializedName; + +public record Alias( + /** Original collection name. */ + @SerializedName("class") String collection, + /** Collection alias. */ + @SerializedName("alias") String alias) { +} diff --git a/src/main/java/io/weaviate/client6/v1/api/alias/CreateAliasRequest.java b/src/main/java/io/weaviate/client6/v1/api/alias/CreateAliasRequest.java new file mode 100644 index 00000000..9b9dff27 --- /dev/null +++ b/src/main/java/io/weaviate/client6/v1/api/alias/CreateAliasRequest.java @@ -0,0 +1,15 @@ +package io.weaviate.client6.v1.api.alias; + +import java.util.Collections; + +import io.weaviate.client6.v1.internal.json.JSON; +import io.weaviate.client6.v1.internal.rest.Endpoint; +import io.weaviate.client6.v1.internal.rest.SimpleEndpoint; + +public record CreateAliasRequest(Alias alias) { + public final static Endpoint _ENDPOINT = SimpleEndpoint.sideEffect( + __ -> "POST", + __ -> "/aliases/", + __ -> Collections.emptyMap(), + request -> JSON.serialize(request.alias)); +} diff --git a/src/main/java/io/weaviate/client6/v1/api/alias/DeleteAliasRequest.java b/src/main/java/io/weaviate/client6/v1/api/alias/DeleteAliasRequest.java new file mode 100644 index 00000000..4b3ed2d3 --- /dev/null +++ b/src/main/java/io/weaviate/client6/v1/api/alias/DeleteAliasRequest.java @@ -0,0 +1,13 @@ +package io.weaviate.client6.v1.api.alias; + +import java.util.Collections; + +import io.weaviate.client6.v1.internal.rest.Endpoint; +import io.weaviate.client6.v1.internal.rest.SimpleEndpoint; + +public record DeleteAliasRequest(String alias) { + public final static Endpoint _ENDPOINT = SimpleEndpoint.sideEffect( + __ -> "DELETE", + request -> "/aliases/" + request.alias, + __ -> Collections.emptyMap()); +} diff --git a/src/main/java/io/weaviate/client6/v1/api/alias/GetAliasRequest.java b/src/main/java/io/weaviate/client6/v1/api/alias/GetAliasRequest.java new file mode 100644 index 00000000..a3ad048b --- /dev/null +++ b/src/main/java/io/weaviate/client6/v1/api/alias/GetAliasRequest.java @@ -0,0 +1,17 @@ +package io.weaviate.client6.v1.api.alias; + +import java.util.Collections; +import java.util.Optional; + +import io.weaviate.client6.v1.internal.json.JSON; +import io.weaviate.client6.v1.internal.rest.Endpoint; +import io.weaviate.client6.v1.internal.rest.OptionalEndpoint; + +public record GetAliasRequest(String alias) { + public final static Endpoint> _ENDPOINT = OptionalEndpoint.noBodyOptional( + __ -> "GET", + request -> "/aliases/" + request.alias, + __ -> Collections.emptyMap(), + (statusCode, response) -> JSON.deserialize(response, Alias.class)); + +} diff --git a/src/main/java/io/weaviate/client6/v1/api/alias/ListAliasRequest.java b/src/main/java/io/weaviate/client6/v1/api/alias/ListAliasRequest.java new file mode 100644 index 00000000..89940269 --- /dev/null +++ b/src/main/java/io/weaviate/client6/v1/api/alias/ListAliasRequest.java @@ -0,0 +1,50 @@ +package io.weaviate.client6.v1.api.alias; + +import java.util.Collections; +import java.util.List; +import java.util.Map; +import java.util.function.Function; + +import io.weaviate.client6.v1.internal.ObjectBuilder; +import io.weaviate.client6.v1.internal.json.JSON; +import io.weaviate.client6.v1.internal.rest.Endpoint; +import io.weaviate.client6.v1.internal.rest.SimpleEndpoint; + +public record ListAliasRequest(String collection) { + public final static Endpoint> _ENDPOINT = SimpleEndpoint.noBody( + __ -> "GET", + __ -> "/aliases", + request -> request.collection != null + ? Map.of("class", request.collection) + : Collections.emptyMap(), + (statusCode, response) -> JSON.deserialize(response, ListAliasResponse.class).aliases()); + + /** Create default ListAliasRequest. */ + public static ListAliasRequest of() { + return of(ObjectBuilder.identity()); + } + + /** Create ListAliasRequest with optional parameters. */ + public static ListAliasRequest of(Function> fn) { + return fn.apply(new Builder()).build(); + } + + public ListAliasRequest(Builder builder) { + this(builder.collection); + } + + public static class Builder implements ObjectBuilder { + private String collection; + + /** Return only aliases which reference this collection. */ + public Builder collection(String collection) { + this.collection = collection; + return this; + } + + @Override + public ListAliasRequest build() { + return new ListAliasRequest(this); + } + } +} diff --git a/src/main/java/io/weaviate/client6/v1/api/alias/ListAliasResponse.java b/src/main/java/io/weaviate/client6/v1/api/alias/ListAliasResponse.java new file mode 100644 index 00000000..f03a4f62 --- /dev/null +++ b/src/main/java/io/weaviate/client6/v1/api/alias/ListAliasResponse.java @@ -0,0 +1,8 @@ +package io.weaviate.client6.v1.api.alias; + +import java.util.List; + +import com.google.gson.annotations.SerializedName; + +public record ListAliasResponse(@SerializedName("aliases") List aliases) { +} diff --git a/src/main/java/io/weaviate/client6/v1/api/alias/UpdateAliasRequest.java b/src/main/java/io/weaviate/client6/v1/api/alias/UpdateAliasRequest.java new file mode 100644 index 00000000..060db8de --- /dev/null +++ b/src/main/java/io/weaviate/client6/v1/api/alias/UpdateAliasRequest.java @@ -0,0 +1,26 @@ +package io.weaviate.client6.v1.api.alias; + +import java.util.Collections; + +import com.google.gson.annotations.SerializedName; + +import io.weaviate.client6.v1.internal.json.JSON; +import io.weaviate.client6.v1.internal.rest.Endpoint; +import io.weaviate.client6.v1.internal.rest.SimpleEndpoint; + +public record UpdateAliasRequest(String alias, String newTargetCollection) { + public final static Endpoint _ENDPOINT = SimpleEndpoint.sideEffect( + __ -> "PUT", + request -> "/aliases/" + request.alias, + __ -> Collections.emptyMap(), + request -> JSON.serialize(request.toRequestBody())); + + private RequestBody toRequestBody() { + return new RequestBody(); + } + + private class RequestBody { + @SerializedName("class") + private final String collection = UpdateAliasRequest.this.newTargetCollection; + } +} diff --git a/src/main/java/io/weaviate/client6/v1/api/alias/WeaviateAliasClient.java b/src/main/java/io/weaviate/client6/v1/api/alias/WeaviateAliasClient.java new file mode 100644 index 00000000..08bcb384 --- /dev/null +++ b/src/main/java/io/weaviate/client6/v1/api/alias/WeaviateAliasClient.java @@ -0,0 +1,124 @@ +package io.weaviate.client6.v1.api.alias; + +import java.io.IOException; +import java.util.List; +import java.util.Optional; +import java.util.function.Function; + +import io.weaviate.client6.v1.api.WeaviateApiException; +import io.weaviate.client6.v1.internal.ObjectBuilder; +import io.weaviate.client6.v1.internal.rest.RestTransport; + +public class WeaviateAliasClient { + private final RestTransport restTransport; + + public WeaviateAliasClient(RestTransport restTransport) { + this.restTransport = restTransport; + } + + /** + * Create a new collection alias. + * + * @param collection Original collection name. + * @param alias Collection alias. + * @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 create(String collection, String alias) throws IOException { + create(new Alias(collection, alias)); + } + + /** + * Create a new collection alias. + * + * @param alias Alias object. + * @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 create(Alias alias) throws IOException { + this.restTransport.performRequest(new CreateAliasRequest(alias), CreateAliasRequest._ENDPOINT); + } + + /** + * List all collection aliases defined in the cluster. + * + * @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 List list() throws IOException { + return list(ListAliasRequest.of()); + } + + /** + * List all collection aliases defined in the cluster. + * + * @param fn Lambda expression for optional parameters. + * @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. + * @return A list of aliases. + */ + public List list(Function> fn) throws IOException { + return list(ListAliasRequest.of(fn)); + } + + private List list(ListAliasRequest request) throws IOException { + return this.restTransport.performRequest(request, ListAliasRequest._ENDPOINT); + } + + /** + * Get alias by name. + * + * @param alias Collection alias. + * @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. + * @return Alias if one exists and empty {@code Optional} otherwise. + */ + public Optional get(String alias) throws IOException { + return this.restTransport.performRequest(new GetAliasRequest(alias), GetAliasRequest._ENDPOINT); + } + + /** + * Change which collection this alias references. + * + * @param alias Collection alias. + * @param newTargetCollection 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 update(String alias, String newTargetCollection) throws IOException { + this.restTransport.performRequest(new UpdateAliasRequest(alias, newTargetCollection), + UpdateAliasRequest._ENDPOINT); + } + + /** + * Delete an alias. The previously aliased collection is not affected. + * + * @param alias Collection alias. + * @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 alias) throws IOException { + this.restTransport.performRequest(new DeleteAliasRequest(alias), DeleteAliasRequest._ENDPOINT); + } +} diff --git a/src/main/java/io/weaviate/client6/v1/api/alias/WeaviateAliasClientAsync.java b/src/main/java/io/weaviate/client6/v1/api/alias/WeaviateAliasClientAsync.java new file mode 100644 index 00000000..b7de85a0 --- /dev/null +++ b/src/main/java/io/weaviate/client6/v1/api/alias/WeaviateAliasClientAsync.java @@ -0,0 +1,100 @@ +package io.weaviate.client6.v1.api.alias; + +import java.util.List; +import java.util.Optional; +import java.util.concurrent.CompletableFuture; +import java.util.function.Function; + +import io.weaviate.client6.v1.internal.ObjectBuilder; +import io.weaviate.client6.v1.internal.rest.RestTransport; + +/** Async client for {@code /aliases} endpoints. */ +public class WeaviateAliasClientAsync { + private final RestTransport restTransport; + + public WeaviateAliasClientAsync(RestTransport restTransport) { + this.restTransport = restTransport; + } + + /** + * Create a new collection alias. + * + * @param collection Original collection name. + * @param alias Collection alias. + * + * @return A future holding the server's response. + */ + public CompletableFuture create(String collection, String alias) { + return create(new Alias(collection, alias)); + } + + /** + * Create a new collection alias. + * + * @param alias Alias object. + * + * @return A future holding the server's response. + */ + public CompletableFuture create(Alias alias) { + return this.restTransport.performRequestAsync(new CreateAliasRequest(alias), CreateAliasRequest._ENDPOINT); + } + + /** + * List all collection aliases defined in the cluster. + * + * @return A future holding the server's response. + */ + public CompletableFuture> list() { + return list(ListAliasRequest.of()); + } + + /** + * List all collection aliases defined in the cluster. + * + * @param fn Lambda expression for optional parameters. + * + * @return A future holding the server's response. + */ + public CompletableFuture> list(Function> fn) { + return list(ListAliasRequest.of(fn)); + } + + private CompletableFuture> list(ListAliasRequest request) { + return this.restTransport.performRequestAsync(request, ListAliasRequest._ENDPOINT); + } + + /** + * Get alias by name. + * + * @param alias Collection alias. + * + * @return A future holding the server's response. + */ + public CompletableFuture> get(String alias) { + return this.restTransport.performRequestAsync(new GetAliasRequest(alias), GetAliasRequest._ENDPOINT); + } + + /** + * Change which collection this alias references. + * + * @param alias Collection alias. + * @param newTargetCollection Collection name. + * + * @return A future holding the server's response. + */ + public CompletableFuture update(String alias, String newTargetCollection) { + return this.restTransport.performRequestAsync(new UpdateAliasRequest(alias, newTargetCollection), + UpdateAliasRequest._ENDPOINT); + } + + /** + * Delete an alias. The previously aliased collection is not affected. + * + * @param alias Collection alias. + * + * @return A future holding the server's response. + */ + public CompletableFuture delete(String alias) { + return this.restTransport.performRequestAsync(new DeleteAliasRequest(alias), DeleteAliasRequest._ENDPOINT); + } +}