From 68729a53cf2fad8724124255c528bf60d102e68d Mon Sep 17 00:00:00 2001 From: Russ Cam Date: Mon, 15 Apr 2024 18:14:33 +1000 Subject: [PATCH 01/45] Add nop slf4j for tests (#28) * Add nop slf4j for tests This commit adds nop slf4j test dependency, to suppress noProviders warning. * fix suggestions --- build.gradle | 4 +++- src/main/java/io/qdrant/client/QdrantClient.java | 8 ++++---- 2 files changed, 7 insertions(+), 5 deletions(-) diff --git a/build.gradle b/build.gradle index 0fd3d164..edabb70b 100644 --- a/build.gradle +++ b/build.gradle @@ -80,6 +80,7 @@ jar { def grpcVersion = '1.59.0' def protobufVersion = '3.24.0' def protocVersion = protobufVersion +def slf4jVersion = '2.0.7' def testcontainersVersion = '1.19.6' def jUnitVersion = '5.8.1' @@ -90,7 +91,7 @@ dependencies { implementation "io.grpc:grpc-services:${grpcVersion}" implementation "io.grpc:grpc-stub:${grpcVersion}" implementation "com.google.guava:guava:30.1-jre" - implementation "org.slf4j:slf4j-api:2.0.7" + implementation "org.slf4j:slf4j-api:${slf4jVersion}" compileOnly "org.apache.tomcat:annotations-api:6.0.53" compileOnly "com.google.code.findbugs:jsr305:3.0.2" @@ -102,6 +103,7 @@ dependencies { testImplementation "io.grpc:grpc-testing:${grpcVersion}" testImplementation "org.junit.jupiter:junit-jupiter-api:${jUnitVersion}" testImplementation "org.mockito:mockito-core:3.4.0" + testImplementation "org.slf4j:slf4j-nop:${slf4jVersion}" testImplementation "org.testcontainers:qdrant:${testcontainersVersion}" testImplementation "org.testcontainers:junit-jupiter:${testcontainersVersion}" diff --git a/src/main/java/io/qdrant/client/QdrantClient.java b/src/main/java/io/qdrant/client/QdrantClient.java index e3f0e568..2f616a68 100644 --- a/src/main/java/io/qdrant/client/QdrantClient.java +++ b/src/main/java/io/qdrant/client/QdrantClient.java @@ -2594,7 +2594,7 @@ public ListenableFuture> discoverAsync(DiscoverPoints request, addLogFailureCallback(future, "Discover"); return Futures.transform( future, - response -> response.getResultList(), + DiscoverResponse::getResultList, MoreExecutors.directExecutor()); } @@ -2644,9 +2644,9 @@ public ListenableFuture> discoverBatchAsync( ListenableFuture future = getPoints(timeout).discoverBatch(requestBuilder.build()); addLogFailureCallback(future, "Discover batch"); return Futures.transform( - future, - response -> response.getResultList(), - MoreExecutors.directExecutor()); + future, + DiscoverBatchResponse::getResultList, + MoreExecutors.directExecutor()); } /** From 9b202e4d12089d027660531746938f17b52ba3b3 Mon Sep 17 00:00:00 2001 From: Russ Cam Date: Mon, 15 Apr 2024 18:15:02 +1000 Subject: [PATCH 02/45] Expose gRPC client on high level client (#27) This commit exposes the low-level gRPC client on the high level client, for two purposes: - Allow access to the underlying gRPC channel. This may be useful for capturing properties about the channel e.g. the channel authority, for metrics. - Allow access to the gRPC client to make requests using the low-level gRPC client in cases where functionality may not yet be exposed by the higher level client. --- .../java/io/qdrant/client/QdrantClient.java | 13 ++++++ .../io/qdrant/client/QdrantGrpcClient.java | 8 ++++ .../io/qdrant/client/QdrantClientTest.java | 40 +++++++++++++++++++ 3 files changed, 61 insertions(+) create mode 100644 src/test/java/io/qdrant/client/QdrantClientTest.java diff --git a/src/main/java/io/qdrant/client/QdrantClient.java b/src/main/java/io/qdrant/client/QdrantClient.java index 2f616a68..b311843e 100644 --- a/src/main/java/io/qdrant/client/QdrantClient.java +++ b/src/main/java/io/qdrant/client/QdrantClient.java @@ -137,6 +137,19 @@ public QdrantClient(QdrantGrpcClient grpcClient) { this.grpcClient = grpcClient; } + /** + * Gets the low-level gRPC client. This is exposed to + *
    + *
  • Allow access to the underlying gRPC channel
  • + *
  • Allow access to the gRPC client to make requests using the low-level gRPC client in cases + * where functionality may not yet be exposed by the higher level client.
  • + *
+ * @return The low-level gRPC client + */ + public QdrantGrpcClient grpcClient() { + return grpcClient; + } + /** * Gets detailed information about the qdrant cluster. * diff --git a/src/main/java/io/qdrant/client/QdrantGrpcClient.java b/src/main/java/io/qdrant/client/QdrantGrpcClient.java index e147e15d..6a8dea8a 100644 --- a/src/main/java/io/qdrant/client/QdrantGrpcClient.java +++ b/src/main/java/io/qdrant/client/QdrantGrpcClient.java @@ -95,6 +95,14 @@ public static Builder newBuilder(String host, int port, boolean useTransportLaye return new Builder(host, port, useTransportLayerSecurity); } + /** + * Gets the channel + * @return the channel + */ + public ManagedChannel channel() { + return channel; + } + /** * Gets the client for qdrant services * @return a new instance of {@link QdrantFutureStub} diff --git a/src/test/java/io/qdrant/client/QdrantClientTest.java b/src/test/java/io/qdrant/client/QdrantClientTest.java new file mode 100644 index 00000000..94ec2150 --- /dev/null +++ b/src/test/java/io/qdrant/client/QdrantClientTest.java @@ -0,0 +1,40 @@ +package io.qdrant.client; + +import io.grpc.Grpc; +import io.grpc.InsecureChannelCredentials; +import io.grpc.ManagedChannel; +import org.junit.jupiter.api.AfterEach; +import org.junit.jupiter.api.Assertions; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; +import org.testcontainers.junit.jupiter.Container; +import org.testcontainers.junit.jupiter.Testcontainers; +import org.testcontainers.qdrant.QdrantContainer; + +@Testcontainers +class QdrantClientTest { + + @Container + private static final QdrantContainer QDRANT_CONTAINER = new QdrantContainer(DockerImage.QDRANT_IMAGE); + private QdrantClient client; + + @BeforeEach + public void setup() { + ManagedChannel channel = Grpc.newChannelBuilder( + QDRANT_CONTAINER.getGrpcHostAddress(), + InsecureChannelCredentials.create()) + .build(); + QdrantGrpcClient grpcClient = QdrantGrpcClient.newBuilder(channel, true).build(); + client = new QdrantClient(grpcClient); + } + + @AfterEach + public void teardown() { + client.close(); + } + + @Test + void canAccessChannelOnGrpcClient() { + Assertions.assertTrue(client.grpcClient().channel().authority().startsWith("localhost")); + } +} From 803a4286a866edd00ea50d87bf106af469bf6d93 Mon Sep 17 00:00:00 2001 From: Anush008 Date: Mon, 15 Apr 2024 18:02:32 +0530 Subject: [PATCH 03/45] chore: bump version gradle.properties --- gradle.properties | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/gradle.properties b/gradle.properties index f8e6727c..3830c303 100644 --- a/gradle.properties +++ b/gradle.properties @@ -5,4 +5,4 @@ qdrantProtosVersion=v1.8.0 qdrantVersion=v1.8.0 # The version of the client to generate -packageVersion=1.8.0 \ No newline at end of file +packageVersion=1.8.1 From cf1c3af041bf5452444a63bbedb8b320a9d5ba3c Mon Sep 17 00:00:00 2001 From: Anush Date: Mon, 22 Apr 2024 18:35:28 +0530 Subject: [PATCH 04/45] v1.9.0 (#29) * v1.9.0 * Bump Qdrant version to 1.9.0 * test: Update API key test --------- Co-authored-by: timvisee --- README.md | 6 +++--- gradle.properties | 6 +++--- src/test/java/io/qdrant/client/ApiKeyTest.java | 2 +- 3 files changed, 7 insertions(+), 7 deletions(-) diff --git a/README.md b/README.md index 13c48b55..bd71fd4c 100644 --- a/README.md +++ b/README.md @@ -34,20 +34,20 @@ To install the library, add the following lines to your build config file. io.qdrant client - 1.7.2 + 1.9.0 ``` #### SBT ```sbt -libraryDependencies += "io.qdrant" % "client" % "1.7.2" +libraryDependencies += "io.qdrant" % "client" % "1.9.0" ``` #### Gradle ```gradle -implementation 'io.qdrant:client:1.7.2' +implementation 'io.qdrant:client:1.9.0' ``` ## 📖 Documentation diff --git a/gradle.properties b/gradle.properties index 3830c303..6c644fa6 100644 --- a/gradle.properties +++ b/gradle.properties @@ -1,8 +1,8 @@ # The version of qdrant to use to download protos -qdrantProtosVersion=v1.8.0 +qdrantProtosVersion=v1.9.0 # The version of qdrant docker image to run integration tests against -qdrantVersion=v1.8.0 +qdrantVersion=v1.9.0 # The version of the client to generate -packageVersion=1.8.1 +packageVersion=1.9.0 diff --git a/src/test/java/io/qdrant/client/ApiKeyTest.java b/src/test/java/io/qdrant/client/ApiKeyTest.java index e56bdd5e..201753f8 100644 --- a/src/test/java/io/qdrant/client/ApiKeyTest.java +++ b/src/test/java/io/qdrant/client/ApiKeyTest.java @@ -61,7 +61,7 @@ public void client_without_api_key_cannot_connect() { Throwable cause = executionException.getCause(); assertEquals(StatusRuntimeException.class, cause.getClass()); StatusRuntimeException statusRuntimeException = (StatusRuntimeException) cause; - assertEquals(Status.Code.PERMISSION_DENIED, statusRuntimeException.getStatus().getCode()); + assertEquals(Status.Code.UNAUTHENTICATED, statusRuntimeException.getStatus().getCode()); } } } From 6a2e33cf74ecf963ce054b1d15ee823bfb8712d6 Mon Sep 17 00:00:00 2001 From: Anush Date: Mon, 13 May 2024 13:35:16 +0530 Subject: [PATCH 05/45] fix: Datetime index creation (#37) --- README.md | 4 +-- .../java/io/qdrant/client/QdrantClient.java | 21 ++++++++++-- .../java/io/qdrant/client/PointsTest.java | 34 +++++++++++++++---- 3 files changed, 49 insertions(+), 10 deletions(-) diff --git a/README.md b/README.md index bd71fd4c..73c53b9d 100644 --- a/README.md +++ b/README.md @@ -14,7 +14,7 @@ Tests Apache 2.0 License Discord - Roadmap 2023 + Roadmap 2024

# Qdrant Java Client @@ -184,4 +184,4 @@ List points = client.searchAsync(SearchPoints.newBuilder() ## ⚖️ LICENSE -Apache 2.0 © [2023](https://github.com/qdrant/java-client/blob/master/LICENSE) +Apache 2.0 © [2024](https://github.com/qdrant/java-client/blob/master/LICENSE) diff --git a/src/main/java/io/qdrant/client/QdrantClient.java b/src/main/java/io/qdrant/client/QdrantClient.java index b311843e..5281e522 100644 --- a/src/main/java/io/qdrant/client/QdrantClient.java +++ b/src/main/java/io/qdrant/client/QdrantClient.java @@ -2212,6 +2212,9 @@ public ListenableFuture createPayloadIndexAsync( case Bool: requestBuilder.setFieldType(FieldType.FieldTypeBool); break; + case Datetime: + requestBuilder.setFieldType(FieldType.FieldTypeDatetime); + break; default: throw new IllegalArgumentException("Invalid schemaType: '" + schemaType + "'"); } @@ -2224,8 +2227,22 @@ public ListenableFuture createPayloadIndexAsync( requestBuilder.setOrdering(WriteOrdering.newBuilder().setType(ordering).build()); } - logger.debug("Create payload field index for '{}' in '{}'", field, collectionName); - ListenableFuture future = getPoints(timeout).createFieldIndex(requestBuilder.build()); + return createPayloadIndexAsync(requestBuilder.build(), timeout); + } + + /** + * Creates a payload field index in a collection. + * + * @param request The create field index request. + * @param timeout The timeout for the call. + * @return a new instance of {@link ListenableFuture} + */ + public ListenableFuture createPayloadIndexAsync( + CreateFieldIndexCollection request, + @Nullable Duration timeout + ) { + logger.debug("Create payload field index for '{}' in '{}'", request.getFieldName(), request.getCollectionName()); + ListenableFuture future = getPoints(timeout).createFieldIndex(request); addLogFailureCallback(future, "Create payload field index"); return Futures.transform(future, PointsOperationResponse::getResult, MoreExecutors.directExecutor()); } diff --git a/src/test/java/io/qdrant/client/PointsTest.java b/src/test/java/io/qdrant/client/PointsTest.java index a2d396ec..53f8bbcc 100644 --- a/src/test/java/io/qdrant/client/PointsTest.java +++ b/src/test/java/io/qdrant/client/PointsTest.java @@ -99,7 +99,7 @@ public void retrieve() throws ExecutionException, InterruptedException { assertEquals(1, points.size()); RetrievedPoint point = points.get(0); assertEquals(id(9), point.getId()); - assertEquals(ImmutableSet.of("foo", "bar"), point.getPayloadMap().keySet()); + assertEquals(ImmutableSet.of("foo", "bar", "date"), point.getPayloadMap().keySet()); assertEquals(value("goodbye"), point.getPayloadMap().get("foo")); assertEquals(value(2), point.getPayloadMap().get("bar")); assertEquals(Vectors.getDefaultInstance(), point.getVectors()); @@ -141,7 +141,7 @@ public void setPayload() throws ExecutionException, InterruptedException { assertEquals(1, points.size()); RetrievedPoint point = points.get(0); assertEquals(id(9), point.getId()); - assertEquals(ImmutableSet.of("foo", "bar"), point.getPayloadMap().keySet()); + assertEquals(ImmutableSet.of("foo", "bar", "date"), point.getPayloadMap().keySet()); assertEquals(value("some bar"), point.getPayloadMap().get("bar")); assertEquals(value("goodbye"), point.getPayloadMap().get("foo")); } @@ -188,7 +188,7 @@ public void deletePayload() throws ExecutionException, InterruptedException { assertEquals(1, points.size()); RetrievedPoint point = points.get(0); assertEquals(id(9), point.getId()); - assertEquals(ImmutableSet.of("bar"), point.getPayloadMap().keySet()); + assertEquals(ImmutableSet.of("bar", "date"), point.getPayloadMap().keySet()); assertEquals(value("some bar"), point.getPayloadMap().get("bar")); } @@ -239,6 +239,26 @@ public void createFieldIndex() throws ExecutionException, InterruptedException { assertEquals(PayloadSchemaType.Integer, collectionInfo.getPayloadSchemaMap().get("bar").getDataType()); } + @Test + public void createDatetimeFieldIndex() throws ExecutionException, InterruptedException { + createAndSeedCollection(testName); + + UpdateResult result = client.createPayloadIndexAsync( + testName, + "date", + PayloadSchemaType.Datetime, + null, + null, + null, + null).get(); + + assertEquals(UpdateStatus.Completed, result.getStatus()); + + CollectionInfo collectionInfo = client.getCollectionInfoAsync(testName).get(); + assertEquals(ImmutableSet.of("date"), collectionInfo.getPayloadSchemaMap().keySet()); + assertEquals(PayloadSchemaType.Datetime, collectionInfo.getPayloadSchemaMap().get("date").getDataType()); + } + @Test public void deleteFieldIndex() throws ExecutionException, InterruptedException { createAndSeedCollection(testName); @@ -277,7 +297,7 @@ public void search() throws ExecutionException, InterruptedException { assertEquals(1, points.size()); ScoredPoint point = points.get(0); assertEquals(id(9), point.getId()); - assertEquals(ImmutableSet.of("foo", "bar"), point.getPayloadMap().keySet()); + assertEquals(ImmutableSet.of("foo", "bar", "date"), point.getPayloadMap().keySet()); assertEquals(value("goodbye"), point.getPayloadMap().get("foo")); assertEquals(value(2), point.getPayloadMap().get("bar")); assertFalse(point.getVectors().hasVector()); @@ -589,7 +609,8 @@ private void createAndSeedCollection(String collectionName) throws ExecutionExce .setVectors(VectorsFactory.vectors(ImmutableList.of(3.5f, 4.5f))) .putAllPayload(ImmutableMap.of( "foo", value("hello"), - "bar", value(1) + "bar", value(1), + "date", value("2021-01-01T00:00:00Z") )) .build(), PointStruct.newBuilder() @@ -597,7 +618,8 @@ private void createAndSeedCollection(String collectionName) throws ExecutionExce .setVectors(VectorsFactory.vectors(ImmutableList.of(10.5f, 11.5f))) .putAllPayload(ImmutableMap.of( "foo", value("goodbye"), - "bar", value(2) + "bar", value(2), + "date", value("2024-01-02T00:00:00Z") )) .build() )).get(); From 38c5e26aab7d5e4e6e7e50a2a98c6beeb4c0b437 Mon Sep 17 00:00:00 2001 From: Anush Date: Mon, 13 May 2024 13:39:16 +0530 Subject: [PATCH 06/45] chore: Bump version gradle.properties (#38) --- gradle.properties | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/gradle.properties b/gradle.properties index 6c644fa6..047ef1f1 100644 --- a/gradle.properties +++ b/gradle.properties @@ -1,8 +1,8 @@ # The version of qdrant to use to download protos -qdrantProtosVersion=v1.9.0 +qdrantProtosVersion=v1.9.2 # The version of qdrant docker image to run integration tests against -qdrantVersion=v1.9.0 +qdrantVersion=v1.9.2 # The version of the client to generate -packageVersion=1.9.0 +packageVersion=1.9.1 From 084af1efbaeabe6e8d6c093436fb7399a730573b Mon Sep 17 00:00:00 2001 From: Andrey Vasnetsov Date: Thu, 20 Jun 2024 11:52:50 +0200 Subject: [PATCH 07/45] test: order-by in scroll (#42) Co-authored-by: Anush --- .idea/gradle.xml | 2 +- .idea/misc.xml | 8 +- .idea/vcs.xml | 2 +- gradle.properties | 4 +- .../java/io/qdrant/client/PointsTest.java | 96 +++++++++++++------ 5 files changed, 77 insertions(+), 35 deletions(-) diff --git a/.idea/gradle.xml b/.idea/gradle.xml index e7c302f9..0befd1f1 100644 --- a/.idea/gradle.xml +++ b/.idea/gradle.xml @@ -16,7 +16,7 @@