.
122 |
123 | ### IntelliJ IDEA
124 |
125 | If you're using [IntelliJ IDEA](https://www.jetbrains.com/idea/), IntelliSense may fail to work correctly due to large source files generated from proto-definitions.
126 |
127 | To resolve this, you can increase the file size limit by configuring IntelliJ IDEA as follows:
128 |
129 | 1. In the top menu, navigate to `Help` -> `Edit Custom Properties`.
130 |
131 | 2. Set the `idea.max.intellisense.filesize` properly to a higher value.
132 |
133 | 
134 |
135 | 3. After saving the changes, restart the IDE to apply the new file size limit.
136 |
--------------------------------------------------------------------------------
/LICENSE:
--------------------------------------------------------------------------------
1 | Apache License
2 | Version 2.0, January 2004
3 | http://www.apache.org/licenses/
4 |
5 | TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION
6 |
7 | 1. Definitions.
8 |
9 | "License" shall mean the terms and conditions for use, reproduction,
10 | and distribution as defined by Sections 1 through 9 of this document.
11 |
12 | "Licensor" shall mean the copyright owner or entity authorized by
13 | the copyright owner that is granting the License.
14 |
15 | "Legal Entity" shall mean the union of the acting entity and all
16 | other entities that control, are controlled by, or are under common
17 | control with that entity. For the purposes of this definition,
18 | "control" means (i) the power, direct or indirect, to cause the
19 | direction or management of such entity, whether by contract or
20 | otherwise, or (ii) ownership of fifty percent (50%) or more of the
21 | outstanding shares, or (iii) beneficial ownership of such entity.
22 |
23 | "You" (or "Your") shall mean an individual or Legal Entity
24 | exercising permissions granted by this License.
25 |
26 | "Source" form shall mean the preferred form for making modifications,
27 | including but not limited to software source code, documentation
28 | source, and configuration files.
29 |
30 | "Object" form shall mean any form resulting from mechanical
31 | transformation or translation of a Source form, including but
32 | not limited to compiled object code, generated documentation,
33 | and conversions to other media types.
34 |
35 | "Work" shall mean the work of authorship, whether in Source or
36 | Object form, made available under the License, as indicated by a
37 | copyright notice that is included in or attached to the work
38 | (an example is provided in the Appendix below).
39 |
40 | "Derivative Works" shall mean any work, whether in Source or Object
41 | form, that is based on (or derived from) the Work and for which the
42 | editorial revisions, annotations, elaborations, or other modifications
43 | represent, as a whole, an original work of authorship. For the purposes
44 | of this License, Derivative Works shall not include works that remain
45 | separable from, or merely link (or bind by name) to the interfaces of,
46 | the Work and Derivative Works thereof.
47 |
48 | "Contribution" shall mean any work of authorship, including
49 | the original version of the Work and any modifications or additions
50 | to that Work or Derivative Works thereof, that is intentionally
51 | submitted to Licensor for inclusion in the Work by the copyright owner
52 | or by an individual or Legal Entity authorized to submit on behalf of
53 | the copyright owner. For the purposes of this definition, "submitted"
54 | means any form of electronic, verbal, or written communication sent
55 | to the Licensor or its representatives, including but not limited to
56 | communication on electronic mailing lists, source code control systems,
57 | and issue tracking systems that are managed by, or on behalf of, the
58 | Licensor for the purpose of discussing and improving the Work, but
59 | excluding communication that is conspicuously marked or otherwise
60 | designated in writing by the copyright owner as "Not a Contribution."
61 |
62 | "Contributor" shall mean Licensor and any individual or Legal Entity
63 | on behalf of whom a Contribution has been received by Licensor and
64 | subsequently incorporated within the Work.
65 |
66 | 2. Grant of Copyright License. Subject to the terms and conditions of
67 | this License, each Contributor hereby grants to You a perpetual,
68 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable
69 | copyright license to reproduce, prepare Derivative Works of,
70 | publicly display, publicly perform, sublicense, and distribute the
71 | Work and such Derivative Works in Source or Object form.
72 |
73 | 3. Grant of Patent License. Subject to the terms and conditions of
74 | this License, each Contributor hereby grants to You a perpetual,
75 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable
76 | (except as stated in this section) patent license to make, have made,
77 | use, offer to sell, sell, import, and otherwise transfer the Work,
78 | where such license applies only to those patent claims licensable
79 | by such Contributor that are necessarily infringed by their
80 | Contribution(s) alone or by combination of their Contribution(s)
81 | with the Work to which such Contribution(s) was submitted. If You
82 | institute patent litigation against any entity (including a
83 | cross-claim or counterclaim in a lawsuit) alleging that the Work
84 | or a Contribution incorporated within the Work constitutes direct
85 | or contributory patent infringement, then any patent licenses
86 | granted to You under this License for that Work shall terminate
87 | as of the date such litigation is filed.
88 |
89 | 4. Redistribution. You may reproduce and distribute copies of the
90 | Work or Derivative Works thereof in any medium, with or without
91 | modifications, and in Source or Object form, provided that You
92 | meet the following conditions:
93 |
94 | (a) You must give any other recipients of the Work or
95 | Derivative Works a copy of this License; and
96 |
97 | (b) You must cause any modified files to carry prominent notices
98 | stating that You changed the files; and
99 |
100 | (c) You must retain, in the Source form of any Derivative Works
101 | that You distribute, all copyright, patent, trademark, and
102 | attribution notices from the Source form of the Work,
103 | excluding those notices that do not pertain to any part of
104 | the Derivative Works; and
105 |
106 | (d) If the Work includes a "NOTICE" text file as part of its
107 | distribution, then any Derivative Works that You distribute must
108 | include a readable copy of the attribution notices contained
109 | within such NOTICE file, excluding those notices that do not
110 | pertain to any part of the Derivative Works, in at least one
111 | of the following places: within a NOTICE text file distributed
112 | as part of the Derivative Works; within the Source form or
113 | documentation, if provided along with the Derivative Works; or,
114 | within a display generated by the Derivative Works, if and
115 | wherever such third-party notices normally appear. The contents
116 | of the NOTICE file are for informational purposes only and
117 | do not modify the License. You may add Your own attribution
118 | notices within Derivative Works that You distribute, alongside
119 | or as an addendum to the NOTICE text from the Work, provided
120 | that such additional attribution notices cannot be construed
121 | as modifying the License.
122 |
123 | You may add Your own copyright statement to Your modifications and
124 | may provide additional or different license terms and conditions
125 | for use, reproduction, or distribution of Your modifications, or
126 | for any such Derivative Works as a whole, provided Your use,
127 | reproduction, and distribution of the Work otherwise complies with
128 | the conditions stated in this License.
129 |
130 | 5. Submission of Contributions. Unless You explicitly state otherwise,
131 | any Contribution intentionally submitted for inclusion in the Work
132 | by You to the Licensor shall be under the terms and conditions of
133 | this License, without any additional terms or conditions.
134 | Notwithstanding the above, nothing herein shall supersede or modify
135 | the terms of any separate license agreement you may have executed
136 | with Licensor regarding such Contributions.
137 |
138 | 6. Trademarks. This License does not grant permission to use the trade
139 | names, trademarks, service marks, or product names of the Licensor,
140 | except as required for reasonable and customary use in describing the
141 | origin of the Work and reproducing the content of the NOTICE file.
142 |
143 | 7. Disclaimer of Warranty. Unless required by applicable law or
144 | agreed to in writing, Licensor provides the Work (and each
145 | Contributor provides its Contributions) on an "AS IS" BASIS,
146 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
147 | implied, including, without limitation, any warranties or conditions
148 | of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A
149 | PARTICULAR PURPOSE. You are solely responsible for determining the
150 | appropriateness of using or redistributing the Work and assume any
151 | risks associated with Your exercise of permissions under this License.
152 |
153 | 8. Limitation of Liability. In no event and under no legal theory,
154 | whether in tort (including negligence), contract, or otherwise,
155 | unless required by applicable law (such as deliberate and grossly
156 | negligent acts) or agreed to in writing, shall any Contributor be
157 | liable to You for damages, including any direct, indirect, special,
158 | incidental, or consequential damages of any character arising as a
159 | result of this License or out of the use or inability to use the
160 | Work (including but not limited to damages for loss of goodwill,
161 | work stoppage, computer failure or malfunction, or any and all
162 | other commercial damages or losses), even if such Contributor
163 | has been advised of the possibility of such damages.
164 |
165 | 9. Accepting Warranty or Additional Liability. While redistributing
166 | the Work or Derivative Works thereof, You may choose to offer,
167 | and charge a fee for, acceptance of support, warranty, indemnity,
168 | or other liability obligations and/or rights consistent with this
169 | License. However, in accepting such obligations, You may act only
170 | on Your own behalf and on Your sole responsibility, not on behalf
171 | of any other Contributor, and only if You agree to indemnify,
172 | defend, and hold each Contributor harmless for any liability
173 | incurred by, or claims asserted against, such Contributor by reason
174 | of your accepting any such warranty or additional liability.
175 |
176 | END OF TERMS AND CONDITIONS
177 |
178 | APPENDIX: How to apply the Apache License to your work.
179 |
180 | To apply the Apache License to your work, attach the following
181 | boilerplate notice, with the fields enclosed by brackets "[]"
182 | replaced with your own identifying information. (Don't include
183 | the brackets!) The text should be enclosed in the appropriate
184 | comment syntax for the file format. We also recommend that a
185 | file or class name and description of purpose be included on the
186 | same "printed page" as the copyright notice for easier
187 | identification within third-party archives.
188 |
189 | Copyright [yyyy] [name of copyright owner]
190 |
191 | Licensed under the Apache License, Version 2.0 (the "License");
192 | you may not use this file except in compliance with the License.
193 | You may obtain a copy of the License at
194 |
195 | http://www.apache.org/licenses/LICENSE-2.0
196 |
197 | Unless required by applicable law or agreed to in writing, software
198 | distributed under the License is distributed on an "AS IS" BASIS,
199 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
200 | See the License for the specific language governing permissions and
201 | limitations under the License.
202 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 | Java library for the Qdrant vector search engine.
10 |
11 |
12 |
13 |
14 |
15 |
16 |
17 |
18 |
19 |
20 | # Qdrant Java Client
21 |
22 | Java client library with handy utility methods and overloads for interfacing with [Qdrant](https://qdrant.tech/).
23 |
24 | ## 📥 Installation
25 |
26 | > [!IMPORTANT]
27 | > Requires Java 8 or above.
28 |
29 | To install the library, add the following lines to your build config file.
30 |
31 | #### Maven
32 |
33 | ```xml
34 |
35 | io.qdrant
36 | client
37 | 1.14.0
38 |
39 | ```
40 |
41 | #### SBT
42 |
43 | ```sbt
44 | libraryDependencies += "io.qdrant" % "client" % "1.14.0"
45 | ```
46 |
47 | #### Gradle
48 |
49 | ```gradle
50 | implementation 'io.qdrant:client:1.14.0'
51 | ```
52 |
53 | > [!NOTE]
54 | > Please make sure to include all necessary dependencies listed [here](https://central.sonatype.com/artifact/io.qdrant/client/dependencies) in your project.
55 |
56 | ## 📖 Documentation
57 |
58 | - Usage examples are available throughout the [Qdrant documentation](https://qdrant.tech/documentation/quick-start/) and [API Reference](https://api.qdrant.tech/).
59 | - [JavaDoc Reference](https://qdrant.github.io/java-client/)
60 |
61 | ## 🔌 Getting started
62 |
63 | ### Creating a client
64 |
65 | A client can be instantiated with
66 |
67 | ```java
68 | QdrantClient client =
69 | new QdrantClient(QdrantGrpcClient.newBuilder("localhost").build());
70 | ```
71 |
72 | which creates a client that will connect to Qdrant on .
73 |
74 | Internally, the high-level client uses a low-level gRPC client to interact with
75 | Qdrant. Additional constructor overloads provide more control over how the gRPC
76 | client is configured. The following example configures a client to use TLS,
77 | validating the certificate using the root CA to verify the server's identity
78 | instead of the system's default, and also configures API key authentication:
79 |
80 | ```java
81 | ManagedChannel channel = Grpc.newChannelBuilder(
82 | "localhost:6334",
83 | TlsChannelCredentials.newBuilder()
84 | .trustManager(new File("ssl/ca.crt"))
85 | .build())
86 | .build();
87 |
88 | QdrantClient client = new QdrantClient(
89 | QdrantGrpcClient.newBuilder(channel)
90 | .withApiKey("")
91 | .build());
92 | ```
93 |
94 | The client implements [`AutoCloseable`](https://docs.oracle.com/javase/8/docs/api/java/lang/AutoCloseable.html),
95 | though a client will typically be created once and used for the lifetime of the
96 | application. When a client is constructed by passing a `ManagedChannel`, the
97 | client does not shut down the channel on close by default. The client can be
98 | configured to shut down the channel on closing with
99 |
100 | ```java
101 | ManagedChannel channel = Grpc.newChannelBuilder(
102 | "localhost:6334",
103 | TlsChannelCredentials.create())
104 | .build();
105 |
106 | QdrantClient client = new QdrantClient(
107 | QdrantGrpcClient.newBuilder(channel, true)
108 | .withApiKey("")
109 | .build());
110 | ```
111 |
112 | All client methods return `ListenableFuture`.
113 |
114 | ### Working with collections
115 |
116 | Once a client has been created, create a new collection
117 |
118 | ```java
119 | client.createCollectionAsync("{collection_name}",
120 | VectorParams.newBuilder()
121 | .setDistance(Distance.Cosine)
122 | .setSize(4)
123 | .build())
124 | .get();
125 | ```
126 |
127 | Insert vectors into a collection
128 |
129 | ```java
130 | // import static convenience methods
131 | import static io.qdrant.client.PointIdFactory.id;
132 | import static io.qdrant.client.ValueFactory.value;
133 | import static io.qdrant.client.VectorsFactory.vectors;
134 |
135 | List points =
136 | List.of(
137 | PointStruct.newBuilder()
138 | .setId(id(1))
139 | .setVectors(vectors(0.32f, 0.52f, 0.21f, 0.52f))
140 | .putAllPayload(
141 | Map.of(
142 | "color", value("red"),
143 | "rand_number", value(32)))
144 | .build(),
145 | PointStruct.newBuilder()
146 | .setId(id(2))
147 | .setVectors(vectors(0.42f, 0.52f, 0.67f, 0.632f))
148 | .putAllPayload(
149 | Map.of(
150 | "color", value("black"),
151 | "rand_number", value(53),
152 | "extra_field", value(true)))
153 | .build());
154 |
155 | UpdateResult updateResult = client.upsertAsync("{collection_name}", points).get();
156 | ```
157 |
158 | Search for similar vectors
159 |
160 | ```java
161 | List points =
162 | client
163 | .searchAsync(
164 | SearchPoints.newBuilder()
165 | .setCollectionName("{collection_name}")
166 | .addAllVector(List.of(0.6235f, 0.123f, 0.532f, 0.123f))
167 | .setLimit(5)
168 | .build())
169 | .get();
170 | ```
171 |
172 | Search for similar vectors with filtering condition
173 |
174 | ```java
175 | // import static convenience methods
176 | import static io.qdrant.client.ConditionFactory.range;
177 |
178 | List points = client.searchAsync(SearchPoints.newBuilder()
179 | .setCollectionName("{collection_name}")
180 | .addAllVector(List.of(0.6235f, 0.123f, 0.532f, 0.123f))
181 | .setFilter(Filter.newBuilder()
182 | .addMust(range("rand_number", Range.newBuilder().setGte(3).build()))
183 | .build())
184 | .setLimit(5)
185 | .build()
186 | ).get();
187 | ```
188 |
189 | ## ⚖️ LICENSE
190 |
191 | [Apache 2.0](https://github.com/qdrant/java-client/blob/master/LICENSE)
192 |
--------------------------------------------------------------------------------
/build.gradle:
--------------------------------------------------------------------------------
1 | import net.ltgt.gradle.errorprone.CheckSeverity
2 | import org.ajoberstar.grgit.Grgit
3 | import org.apache.commons.compress.archivers.tar.TarArchiveEntry
4 | import org.apache.commons.compress.archivers.tar.TarArchiveInputStream
5 | import org.apache.commons.compress.compressors.gzip.GzipCompressorInputStream
6 | import org.apache.http.client.methods.HttpGet
7 | import org.apache.http.impl.client.HttpClients
8 |
9 | import java.nio.file.Files
10 | import java.nio.file.Paths
11 | import java.nio.file.StandardOpenOption
12 | import java.time.ZoneOffset
13 | import java.util.regex.Pattern
14 |
15 | plugins {
16 | id 'java-library'
17 | id 'idea'
18 | id 'signing'
19 | id 'maven-publish'
20 |
21 | id 'com.google.protobuf' version '0.9.4'
22 | id 'net.ltgt.errorprone' version '3.1.0'
23 | id 'io.github.gradle-nexus.publish-plugin' version '1.3.0'
24 | id 'com.diffplug.spotless' version '6.22.0'
25 | }
26 |
27 | group = 'io.qdrant'
28 | version = packageVersion
29 | description = 'Official Java client for Qdrant vector database'
30 |
31 | repositories {
32 | // google mirror for maven
33 | maven {
34 | url 'https://maven-central.storage-download.googleapis.com/maven2/'
35 | }
36 | mavenCentral()
37 | mavenLocal()
38 | }
39 |
40 | java {
41 | sourceCompatibility = JavaVersion.VERSION_1_8
42 | targetCompatibility = JavaVersion.VERSION_1_8
43 |
44 | withJavadocJar()
45 | withSourcesJar()
46 | }
47 |
48 | tasks.withType(JavaCompile).configureEach {
49 | // exclude generated code from error prone checks
50 | options.errorprone.excludedPaths.set(".*/build/generated/.*")
51 | options.errorprone {
52 | //noinspection GroovyAssignabilityCheck
53 | check("NullAway", CheckSeverity.ERROR)
54 | option("NullAway:AnnotatedPackages", "com.uber")
55 | }
56 | }
57 |
58 | javadoc {
59 | // exclude code generated from protos
60 | exclude 'io/qdrant/client/grpc/**'
61 | exclude 'grpc/**'
62 | }
63 |
64 | sourcesJar {
65 | // exclude generated duplicate of com/qdrant/client/grpc/CollectionsGrpc.java
66 | duplicatesStrategy = DuplicatesStrategy.EXCLUDE
67 | }
68 |
69 | jar {
70 | doFirst {
71 | def git = Grgit.open(Map.of('currentDir', rootProject.rootDir))
72 | // add qdrant version from which client is generated.
73 | jar.manifest.attributes['X-Qdrant-Version'] = qdrantProtosVersion
74 | // add git revision and commit time to jar manifest
75 | jar.manifest.attributes['X-Git-Revision'] = git.head().id
76 | jar.manifest.attributes['X-Git-Commit-Time'] = git.head().dateTime.withZoneSameLocal(ZoneOffset.UTC)
77 | jar.manifest.attributes['Implementation-Version'] = packageVersion
78 | git.close()
79 | }
80 | }
81 |
82 | def grpcVersion = '1.65.1'
83 | def protocVersion = '3.25.4'
84 | def slf4jVersion = '2.0.14'
85 | def testcontainersVersion = '1.20.1'
86 | def jUnitVersion = '5.10.2'
87 |
88 | dependencies {
89 | errorprone "com.uber.nullaway:nullaway:0.10.18"
90 |
91 | implementation "io.grpc:grpc-protobuf:${grpcVersion}"
92 | implementation "io.grpc:grpc-stub:${grpcVersion}"
93 | implementation "org.slf4j:slf4j-api:${slf4jVersion}"
94 |
95 | compileOnly "org.apache.tomcat:annotations-api:6.0.53"
96 |
97 | errorprone "com.google.errorprone:error_prone_core:2.29.2"
98 |
99 | runtimeOnly "io.grpc:grpc-netty-shaded:${grpcVersion}"
100 |
101 | testImplementation "io.grpc:grpc-testing:${grpcVersion}"
102 | testImplementation "org.junit.jupiter:junit-jupiter-api:${jUnitVersion}"
103 | testImplementation "org.junit.jupiter:junit-jupiter-params:${jUnitVersion}"
104 | testImplementation "org.mockito:mockito-core:3.4.0"
105 | testImplementation "org.slf4j:slf4j-nop:${slf4jVersion}"
106 | testImplementation "org.testcontainers:qdrant:${testcontainersVersion}"
107 | testImplementation "org.testcontainers:junit-jupiter:${testcontainersVersion}"
108 |
109 | testRuntimeOnly "org.junit.jupiter:junit-jupiter-engine:${jUnitVersion}"
110 | }
111 |
112 | tasks.register('downloadProtos') {
113 | // gradle detects changes to this output dir since last run to determine whether to run this task
114 | outputs.dir(new File(rootProject.rootDir, "protos/${qdrantProtosVersion}"))
115 |
116 | doLast {
117 | def outputDirectory = outputs.files.singleFile.toPath().toString()
118 | def protoFileRegex = Pattern.compile(".*?lib/api/src/grpc/proto/.*?.proto")
119 | try (def httpClient = HttpClients.createDefault()) {
120 | def url = "https://api.github.com/repos/qdrant/qdrant/tarball/${qdrantProtosVersion}"
121 | logger.debug("downloading protos from {}", url)
122 | def response = httpClient.execute(new HttpGet(url))
123 | try (InputStream tarballStream = response.getEntity().getContent()) {
124 | try (TarArchiveInputStream tarInput = new TarArchiveInputStream(new GzipCompressorInputStream(tarballStream))) {
125 | TarArchiveEntry entry
126 | while ((entry = tarInput.getNextTarEntry()) != null) {
127 | if (!entry.isDirectory() && protoFileRegex.matcher(entry.getName()).matches()) {
128 | def lines = new ArrayList()
129 | def lineNum = -1
130 | def seenJavaPackage = false
131 | def br = new BufferedReader(new InputStreamReader(tarInput))
132 | String line
133 | while ((line = br.readLine()) != null) {
134 | lines.add(line)
135 | if (line == "package qdrant;") {
136 | lineNum = lines.size()
137 | } else if (line.startsWith("option java_package")) {
138 | seenJavaPackage = true
139 | }
140 | }
141 | // patch in java package to qdrant protos
142 | if (!seenJavaPackage && lineNum != -1) {
143 | lines.add(lineNum, "option java_package = \"io.qdrant.client.grpc\";")
144 | }
145 |
146 | def fileName = Paths.get(entry.getName()).getFileName().toString()
147 | def dest = java.nio.file.Path.of(outputDirectory, fileName)
148 | logger.debug("writing {} to {}", fileName, dest)
149 | Files.write(dest, lines, StandardOpenOption.CREATE)
150 | }
151 | }
152 | }
153 | }
154 | }
155 | }
156 | }
157 |
158 | processResources {
159 | dependsOn downloadProtos
160 | }
161 |
162 | extractIncludeProto {
163 | dependsOn downloadProtos
164 | }
165 |
166 | protobuf {
167 | protoc { artifact = "com.google.protobuf:protoc:${protocVersion}" }
168 | plugins {
169 | grpc { artifact = "io.grpc:protoc-gen-grpc-java:${grpcVersion}" }
170 | }
171 | generateProtoTasks {
172 | all()*.plugins { grpc {} }
173 | }
174 | }
175 |
176 | // Inform IDEs like IntelliJ IDEA, Eclipse or NetBeans about the generated code.
177 | sourceSets {
178 | main {
179 | proto {
180 | // include protos from outside the sourceSets
181 | //noinspection GroovyAssignabilityCheck
182 | srcDir "protos/${qdrantProtosVersion}"
183 | }
184 | java {
185 | srcDirs 'build/generated/source/proto/main/grpc'
186 | srcDirs 'build/generated/source/proto/main/java'
187 | }
188 | }
189 | }
190 |
191 | test {
192 | useJUnitPlatform()
193 |
194 | // Set system property to use as docker image version for integration tests
195 | systemProperty 'qdrantVersion', qdrantVersion
196 | }
197 |
198 | def organization = 'qdrant'
199 | def repository = 'java-client'
200 |
201 | publishing {
202 | publications {
203 | mavenJava(MavenPublication) {
204 | from components.java
205 | pom {
206 | name = "Qdrant Java Client"
207 | description = "${project.description}"
208 | url = "https://github.com/${organization}/${repository}"
209 | licenses {
210 | license {
211 | name = 'The Apache License, Version 2.0'
212 | url = 'http://www.apache.org/licenses/LICENSE-2.0.txt'
213 | distribution = 'repo'
214 | }
215 | }
216 | developers {
217 | developer {
218 | id = 'qdrant'
219 | name = 'Qdrant and Contributors'
220 | email = 'info@qdrant.com'
221 | }
222 | }
223 | scm {
224 | connection = "scm:git:git://github.com/${organization}/${repository}.git"
225 | developerConnection = "scm:git:ssh://github.com/${organization}/${repository}.git"
226 | url = "https://github.com/${organization}/${repository}"
227 | }
228 | }
229 | }
230 | }
231 | repositories {
232 | mavenLocal()
233 | }
234 | }
235 |
236 | nexusPublishing {
237 | repositories {
238 | sonatype {
239 | nexusUrl = uri("https://s01.oss.sonatype.org/service/local/")
240 | snapshotRepositoryUrl = uri("https://s01.oss.sonatype.org/content/repositories/snapshots/")
241 | }
242 | }
243 | }
244 |
245 | signing {
246 | def signingKeyId = findProperty("signingKeyId")
247 | def signingKey = findProperty("signingKey")
248 | def signingPassword = findProperty("signingPassword")
249 | useInMemoryPgpKeys(signingKeyId, signingKey, signingPassword)
250 | sign publishing.publications.mavenJava
251 | }
252 |
253 | spotless {
254 | java {
255 | target 'src/*/java/**/*.java'
256 | importOrder()
257 | removeUnusedImports()
258 | cleanthat()
259 | googleJavaFormat()
260 | formatAnnotations()
261 | }
262 | }
263 |
264 | compileJava.dependsOn 'spotlessApply'
265 |
--------------------------------------------------------------------------------
/buildSrc/build.gradle:
--------------------------------------------------------------------------------
1 | dependencies {
2 | implementation 'org.ajoberstar.grgit:grgit-gradle:5.0.0'
3 | implementation 'org.apache.httpcomponents:httpclient:4.5.14'
4 | implementation 'org.apache.commons:commons-compress:1.23.0'
5 | }
6 |
7 | repositories {
8 | mavenCentral()
9 | }
--------------------------------------------------------------------------------
/gradle.properties:
--------------------------------------------------------------------------------
1 | # The version of qdrant to use to download protos
2 | qdrantProtosVersion=v1.14.0
3 |
4 | # The version of qdrant docker image to run integration tests against
5 | qdrantVersion=v1.14.0
6 |
7 | # The version of the client to generate
8 | packageVersion=1.14.0
9 |
--------------------------------------------------------------------------------
/gradle/wrapper/gradle-wrapper.jar:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/qdrant/java-client/38fd5de62121c8e8b0f1bcb6b89adb39542abbc1/gradle/wrapper/gradle-wrapper.jar
--------------------------------------------------------------------------------
/gradle/wrapper/gradle-wrapper.properties:
--------------------------------------------------------------------------------
1 | distributionBase=GRADLE_USER_HOME
2 | distributionPath=wrapper/dists
3 | distributionUrl=https\://services.gradle.org/distributions/gradle-8.5-bin.zip
4 | networkTimeout=10000
5 | validateDistributionUrl=true
6 | zipStoreBase=GRADLE_USER_HOME
7 | zipStorePath=wrapper/dists
8 |
--------------------------------------------------------------------------------
/gradlew:
--------------------------------------------------------------------------------
1 | #!/bin/sh
2 |
3 | #
4 | # Copyright © 2015-2021 the original authors.
5 | #
6 | # Licensed under the Apache License, Version 2.0 (the "License");
7 | # you may not use this file except in compliance with the License.
8 | # You may obtain a copy of the License at
9 | #
10 | # https://www.apache.org/licenses/LICENSE-2.0
11 | #
12 | # Unless required by applicable law or agreed to in writing, software
13 | # distributed under the License is distributed on an "AS IS" BASIS,
14 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
15 | # See the License for the specific language governing permissions and
16 | # limitations under the License.
17 | #
18 |
19 | ##############################################################################
20 | #
21 | # Gradle start up script for POSIX generated by Gradle.
22 | #
23 | # Important for running:
24 | #
25 | # (1) You need a POSIX-compliant shell to run this script. If your /bin/sh is
26 | # noncompliant, but you have some other compliant shell such as ksh or
27 | # bash, then to run this script, type that shell name before the whole
28 | # command line, like:
29 | #
30 | # ksh Gradle
31 | #
32 | # Busybox and similar reduced shells will NOT work, because this script
33 | # requires all of these POSIX shell features:
34 | # * functions;
35 | # * expansions «$var», «${var}», «${var:-default}», «${var+SET}»,
36 | # «${var#prefix}», «${var%suffix}», and «$( cmd )»;
37 | # * compound commands having a testable exit status, especially «case»;
38 | # * various built-in commands including «command», «set», and «ulimit».
39 | #
40 | # Important for patching:
41 | #
42 | # (2) This script targets any POSIX shell, so it avoids extensions provided
43 | # by Bash, Ksh, etc; in particular arrays are avoided.
44 | #
45 | # The "traditional" practice of packing multiple parameters into a
46 | # space-separated string is a well documented source of bugs and security
47 | # problems, so this is (mostly) avoided, by progressively accumulating
48 | # options in "$@", and eventually passing that to Java.
49 | #
50 | # Where the inherited environment variables (DEFAULT_JVM_OPTS, JAVA_OPTS,
51 | # and GRADLE_OPTS) rely on word-splitting, this is performed explicitly;
52 | # see the in-line comments for details.
53 | #
54 | # There are tweaks for specific operating systems such as AIX, CygWin,
55 | # Darwin, MinGW, and NonStop.
56 | #
57 | # (3) This script is generated from the Groovy template
58 | # https://github.com/gradle/gradle/blob/HEAD/subprojects/plugins/src/main/resources/org/gradle/api/internal/plugins/unixStartScript.txt
59 | # within the Gradle project.
60 | #
61 | # You can find Gradle at https://github.com/gradle/gradle/.
62 | #
63 | ##############################################################################
64 |
65 | # Attempt to set APP_HOME
66 |
67 | # Resolve links: $0 may be a link
68 | app_path=$0
69 |
70 | # Need this for daisy-chained symlinks.
71 | while
72 | APP_HOME=${app_path%"${app_path##*/}"} # leaves a trailing /; empty if no leading path
73 | [ -h "$app_path" ]
74 | do
75 | ls=$( ls -ld "$app_path" )
76 | link=${ls#*' -> '}
77 | case $link in #(
78 | /*) app_path=$link ;; #(
79 | *) app_path=$APP_HOME$link ;;
80 | esac
81 | done
82 |
83 | # This is normally unused
84 | # shellcheck disable=SC2034
85 | APP_BASE_NAME=${0##*/}
86 | APP_HOME=$( cd "${APP_HOME:-./}" && pwd -P ) || exit
87 |
88 | # Use the maximum available, or set MAX_FD != -1 to use that value.
89 | MAX_FD=maximum
90 |
91 | warn () {
92 | echo "$*"
93 | } >&2
94 |
95 | die () {
96 | echo
97 | echo "$*"
98 | echo
99 | exit 1
100 | } >&2
101 |
102 | # OS specific support (must be 'true' or 'false').
103 | cygwin=false
104 | msys=false
105 | darwin=false
106 | nonstop=false
107 | case "$( uname )" in #(
108 | CYGWIN* ) cygwin=true ;; #(
109 | Darwin* ) darwin=true ;; #(
110 | MSYS* | MINGW* ) msys=true ;; #(
111 | NONSTOP* ) nonstop=true ;;
112 | esac
113 |
114 | CLASSPATH=$APP_HOME/gradle/wrapper/gradle-wrapper.jar
115 |
116 |
117 | # Determine the Java command to use to start the JVM.
118 | if [ -n "$JAVA_HOME" ] ; then
119 | if [ -x "$JAVA_HOME/jre/sh/java" ] ; then
120 | # IBM's JDK on AIX uses strange locations for the executables
121 | JAVACMD=$JAVA_HOME/jre/sh/java
122 | else
123 | JAVACMD=$JAVA_HOME/bin/java
124 | fi
125 | if [ ! -x "$JAVACMD" ] ; then
126 | die "ERROR: JAVA_HOME is set to an invalid directory: $JAVA_HOME
127 |
128 | Please set the JAVA_HOME variable in your environment to match the
129 | location of your Java installation."
130 | fi
131 | else
132 | JAVACMD=java
133 | if ! command -v java >/dev/null 2>&1
134 | then
135 | die "ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH.
136 |
137 | Please set the JAVA_HOME variable in your environment to match the
138 | location of your Java installation."
139 | fi
140 | fi
141 |
142 | # Increase the maximum file descriptors if we can.
143 | if ! "$cygwin" && ! "$darwin" && ! "$nonstop" ; then
144 | case $MAX_FD in #(
145 | max*)
146 | # In POSIX sh, ulimit -H is undefined. That's why the result is checked to see if it worked.
147 | # shellcheck disable=SC3045
148 | MAX_FD=$( ulimit -H -n ) ||
149 | warn "Could not query maximum file descriptor limit"
150 | esac
151 | case $MAX_FD in #(
152 | '' | soft) :;; #(
153 | *)
154 | # In POSIX sh, ulimit -n is undefined. That's why the result is checked to see if it worked.
155 | # shellcheck disable=SC3045
156 | ulimit -n "$MAX_FD" ||
157 | warn "Could not set maximum file descriptor limit to $MAX_FD"
158 | esac
159 | fi
160 |
161 | # Collect all arguments for the java command, stacking in reverse order:
162 | # * args from the command line
163 | # * the main class name
164 | # * -classpath
165 | # * -D...appname settings
166 | # * --module-path (only if needed)
167 | # * DEFAULT_JVM_OPTS, JAVA_OPTS, and GRADLE_OPTS environment variables.
168 |
169 | # For Cygwin or MSYS, switch paths to Windows format before running java
170 | if "$cygwin" || "$msys" ; then
171 | APP_HOME=$( cygpath --path --mixed "$APP_HOME" )
172 | CLASSPATH=$( cygpath --path --mixed "$CLASSPATH" )
173 |
174 | JAVACMD=$( cygpath --unix "$JAVACMD" )
175 |
176 | # Now convert the arguments - kludge to limit ourselves to /bin/sh
177 | for arg do
178 | if
179 | case $arg in #(
180 | -*) false ;; # don't mess with options #(
181 | /?*) t=${arg#/} t=/${t%%/*} # looks like a POSIX filepath
182 | [ -e "$t" ] ;; #(
183 | *) false ;;
184 | esac
185 | then
186 | arg=$( cygpath --path --ignore --mixed "$arg" )
187 | fi
188 | # Roll the args list around exactly as many times as the number of
189 | # args, so each arg winds up back in the position where it started, but
190 | # possibly modified.
191 | #
192 | # NB: a `for` loop captures its iteration list before it begins, so
193 | # changing the positional parameters here affects neither the number of
194 | # iterations, nor the values presented in `arg`.
195 | shift # remove old arg
196 | set -- "$@" "$arg" # push replacement arg
197 | done
198 | fi
199 |
200 |
201 | # Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script.
202 | DEFAULT_JVM_OPTS='"-Xmx64m" "-Xms64m"'
203 |
204 | # Collect all arguments for the java command;
205 | # * $DEFAULT_JVM_OPTS, $JAVA_OPTS, and $GRADLE_OPTS can contain fragments of
206 | # shell script including quotes and variable substitutions, so put them in
207 | # double quotes to make sure that they get re-expanded; and
208 | # * put everything else in single quotes, so that it's not re-expanded.
209 |
210 | set -- \
211 | "-Dorg.gradle.appname=$APP_BASE_NAME" \
212 | -classpath "$CLASSPATH" \
213 | org.gradle.wrapper.GradleWrapperMain \
214 | "$@"
215 |
216 | # Stop when "xargs" is not available.
217 | if ! command -v xargs >/dev/null 2>&1
218 | then
219 | die "xargs is not available"
220 | fi
221 |
222 | # Use "xargs" to parse quoted args.
223 | #
224 | # With -n1 it outputs one arg per line, with the quotes and backslashes removed.
225 | #
226 | # In Bash we could simply go:
227 | #
228 | # readarray ARGS < <( xargs -n1 <<<"$var" ) &&
229 | # set -- "${ARGS[@]}" "$@"
230 | #
231 | # but POSIX shell has neither arrays nor command substitution, so instead we
232 | # post-process each arg (as a line of input to sed) to backslash-escape any
233 | # character that might be a shell metacharacter, then use eval to reverse
234 | # that process (while maintaining the separation between arguments), and wrap
235 | # the whole thing up as a single "set" statement.
236 | #
237 | # This will of course break if any of these variables contains a newline or
238 | # an unmatched quote.
239 | #
240 |
241 | eval "set -- $(
242 | printf '%s\n' "$DEFAULT_JVM_OPTS $JAVA_OPTS $GRADLE_OPTS" |
243 | xargs -n1 |
244 | sed ' s~[^-[:alnum:]+,./:=@_]~\\&~g; ' |
245 | tr '\n' ' '
246 | )" '"$@"'
247 |
248 | exec "$JAVACMD" "$@"
249 |
--------------------------------------------------------------------------------
/gradlew.bat:
--------------------------------------------------------------------------------
1 | @rem
2 | @rem Copyright 2015 the original author or authors.
3 | @rem
4 | @rem Licensed under the Apache License, Version 2.0 (the "License");
5 | @rem you may not use this file except in compliance with the License.
6 | @rem You may obtain a copy of the License at
7 | @rem
8 | @rem https://www.apache.org/licenses/LICENSE-2.0
9 | @rem
10 | @rem Unless required by applicable law or agreed to in writing, software
11 | @rem distributed under the License is distributed on an "AS IS" BASIS,
12 | @rem WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 | @rem See the License for the specific language governing permissions and
14 | @rem limitations under the License.
15 | @rem
16 |
17 | @if "%DEBUG%"=="" @echo off
18 | @rem ##########################################################################
19 | @rem
20 | @rem Gradle startup script for Windows
21 | @rem
22 | @rem ##########################################################################
23 |
24 | @rem Set local scope for the variables with windows NT shell
25 | if "%OS%"=="Windows_NT" setlocal
26 |
27 | set DIRNAME=%~dp0
28 | if "%DIRNAME%"=="" set DIRNAME=.
29 | @rem This is normally unused
30 | set APP_BASE_NAME=%~n0
31 | set APP_HOME=%DIRNAME%
32 |
33 | @rem Resolve any "." and ".." in APP_HOME to make it shorter.
34 | for %%i in ("%APP_HOME%") do set APP_HOME=%%~fi
35 |
36 | @rem Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script.
37 | set DEFAULT_JVM_OPTS="-Xmx64m" "-Xms64m"
38 |
39 | @rem Find java.exe
40 | if defined JAVA_HOME goto findJavaFromJavaHome
41 |
42 | set JAVA_EXE=java.exe
43 | %JAVA_EXE% -version >NUL 2>&1
44 | if %ERRORLEVEL% equ 0 goto execute
45 |
46 | echo.
47 | echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH.
48 | echo.
49 | echo Please set the JAVA_HOME variable in your environment to match the
50 | echo location of your Java installation.
51 |
52 | goto fail
53 |
54 | :findJavaFromJavaHome
55 | set JAVA_HOME=%JAVA_HOME:"=%
56 | set JAVA_EXE=%JAVA_HOME%/bin/java.exe
57 |
58 | if exist "%JAVA_EXE%" goto execute
59 |
60 | echo.
61 | echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME%
62 | echo.
63 | echo Please set the JAVA_HOME variable in your environment to match the
64 | echo location of your Java installation.
65 |
66 | goto fail
67 |
68 | :execute
69 | @rem Setup the command line
70 |
71 | set CLASSPATH=%APP_HOME%\gradle\wrapper\gradle-wrapper.jar
72 |
73 |
74 | @rem Execute Gradle
75 | "%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -classpath "%CLASSPATH%" org.gradle.wrapper.GradleWrapperMain %*
76 |
77 | :end
78 | @rem End local scope for the variables with windows NT shell
79 | if %ERRORLEVEL% equ 0 goto mainEnd
80 |
81 | :fail
82 | rem Set variable GRADLE_EXIT_CONSOLE if you need the _script_ return code instead of
83 | rem the _cmd.exe /c_ return code!
84 | set EXIT_CODE=%ERRORLEVEL%
85 | if %EXIT_CODE% equ 0 set EXIT_CODE=1
86 | if not ""=="%GRADLE_EXIT_CONSOLE%" exit %EXIT_CODE%
87 | exit /b %EXIT_CODE%
88 |
89 | :mainEnd
90 | if "%OS%"=="Windows_NT" endlocal
91 |
92 | :omega
93 |
--------------------------------------------------------------------------------
/resources/java-logo-small.svg:
--------------------------------------------------------------------------------
1 |
2 |
3 |
19 |
21 |
40 |
42 |
43 |
45 | image/svg+xml
46 |
48 |
49 |
50 |
51 |
56 |
59 |
61 |
62 |
64 |
68 |
69 |
73 |
74 |
75 |
76 |
77 |
78 |
79 |
--------------------------------------------------------------------------------
/resources/java-logo.svg:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/settings.gradle:
--------------------------------------------------------------------------------
1 | rootProject.name = 'client'
2 |
3 |
--------------------------------------------------------------------------------
/src/main/java/io/qdrant/client/ApiKeyCredentials.java:
--------------------------------------------------------------------------------
1 | package io.qdrant.client;
2 |
3 | import io.grpc.CallCredentials;
4 | import io.grpc.Metadata;
5 | import io.grpc.Status;
6 | import java.util.concurrent.Executor;
7 |
8 | /** API key authentication credentials */
9 | public class ApiKeyCredentials extends CallCredentials {
10 | private final String apiKey;
11 |
12 | /**
13 | * Instantiates a new instance of {@link ApiKeyCredentials}
14 | *
15 | * @param apiKey The API key to use for authentication
16 | */
17 | public ApiKeyCredentials(String apiKey) {
18 | this.apiKey = apiKey;
19 | }
20 |
21 | @Override
22 | public void applyRequestMetadata(
23 | RequestInfo requestInfo, Executor appExecutor, MetadataApplier applier) {
24 | appExecutor.execute(
25 | () -> {
26 | try {
27 | Metadata headers = new Metadata();
28 | headers.put(Metadata.Key.of("api-key", Metadata.ASCII_STRING_MARSHALLER), apiKey);
29 | applier.apply(headers);
30 | } catch (Throwable e) {
31 | applier.fail(Status.UNAUTHENTICATED.withCause(e));
32 | }
33 | });
34 | }
35 | }
36 |
--------------------------------------------------------------------------------
/src/main/java/io/qdrant/client/ConditionFactory.java:
--------------------------------------------------------------------------------
1 | package io.qdrant.client;
2 |
3 | import io.qdrant.client.grpc.Points.Condition;
4 | import io.qdrant.client.grpc.Points.DatetimeRange;
5 | import io.qdrant.client.grpc.Points.FieldCondition;
6 | import io.qdrant.client.grpc.Points.Filter;
7 | import io.qdrant.client.grpc.Points.GeoBoundingBox;
8 | import io.qdrant.client.grpc.Points.GeoLineString;
9 | import io.qdrant.client.grpc.Points.GeoPoint;
10 | import io.qdrant.client.grpc.Points.GeoPolygon;
11 | import io.qdrant.client.grpc.Points.GeoRadius;
12 | import io.qdrant.client.grpc.Points.HasIdCondition;
13 | import io.qdrant.client.grpc.Points.HasVectorCondition;
14 | import io.qdrant.client.grpc.Points.IsEmptyCondition;
15 | import io.qdrant.client.grpc.Points.IsNullCondition;
16 | import io.qdrant.client.grpc.Points.Match;
17 | import io.qdrant.client.grpc.Points.NestedCondition;
18 | import io.qdrant.client.grpc.Points.PointId;
19 | import io.qdrant.client.grpc.Points.Range;
20 | import io.qdrant.client.grpc.Points.RepeatedIntegers;
21 | import io.qdrant.client.grpc.Points.RepeatedStrings;
22 | import io.qdrant.client.grpc.Points.ValuesCount;
23 | import java.util.List;
24 |
25 | /** Convenience methods for constructing {@link Condition} */
26 | public final class ConditionFactory {
27 | private ConditionFactory() {}
28 |
29 | /**
30 | * Match all records with the provided id
31 | *
32 | * @param id The id to match
33 | * @return a new instance of {@link Condition}
34 | */
35 | public static Condition hasId(PointId id) {
36 | return Condition.newBuilder()
37 | .setHasId(HasIdCondition.newBuilder().addHasId(id).build())
38 | .build();
39 | }
40 |
41 | /**
42 | * Match all records with the provided ids
43 | *
44 | * @param ids The ids to match
45 | * @return a new instance of {@link Condition}
46 | */
47 | public static Condition hasId(List ids) {
48 | return Condition.newBuilder()
49 | .setHasId(HasIdCondition.newBuilder().addAllHasId(ids).build())
50 | .build();
51 | }
52 |
53 | /**
54 | * Match all records where the given field either does not exist, or has null or empty value.
55 | *
56 | * @param field The name of the field
57 | * @return a new instance of {@link Condition}
58 | */
59 | public static Condition isEmpty(String field) {
60 | return Condition.newBuilder()
61 | .setIsEmpty(IsEmptyCondition.newBuilder().setKey(field).build())
62 | .build();
63 | }
64 |
65 | /**
66 | * Match all records where the given field is null.
67 | *
68 | * @param field The name of the field
69 | * @return a new instance of {@link Condition}
70 | */
71 | public static Condition isNull(String field) {
72 | return Condition.newBuilder()
73 | .setIsNull(IsNullCondition.newBuilder().setKey(field).build())
74 | .build();
75 | }
76 |
77 | /**
78 | * Match records where the given field matches the given keyword
79 | *
80 | * @param field The name of the field
81 | * @param keyword The keyword to match
82 | * @return a new instance of {@link Condition}
83 | */
84 | public static Condition matchKeyword(String field, String keyword) {
85 | return Condition.newBuilder()
86 | .setField(
87 | FieldCondition.newBuilder()
88 | .setKey(field)
89 | .setMatch(Match.newBuilder().setKeyword(keyword).build())
90 | .build())
91 | .build();
92 | }
93 |
94 | /**
95 | * Match records where the given field matches the given text.
96 | *
97 | * @param field The name of the field
98 | * @param text The text to match
99 | * @return a new instance of {@link Condition}
100 | */
101 | public static Condition matchText(String field, String text) {
102 | return Condition.newBuilder()
103 | .setField(
104 | FieldCondition.newBuilder()
105 | .setKey(field)
106 | .setMatch(Match.newBuilder().setText(text).build())
107 | .build())
108 | .build();
109 | }
110 |
111 | /**
112 | * Match records where the given field matches the given boolean value.
113 | *
114 | * @param field The name of the field
115 | * @param value The value to match
116 | * @return a new instance of {@link Condition}
117 | */
118 | public static Condition match(String field, boolean value) {
119 | return Condition.newBuilder()
120 | .setField(
121 | FieldCondition.newBuilder()
122 | .setKey(field)
123 | .setMatch(Match.newBuilder().setBoolean(value).build())
124 | .build())
125 | .build();
126 | }
127 |
128 | /**
129 | * Match records where the given field matches the given long value.
130 | *
131 | * @param field The name of the field
132 | * @param value The value to match
133 | * @return a new instance of {@link Condition}
134 | */
135 | public static Condition match(String field, long value) {
136 | return Condition.newBuilder()
137 | .setField(
138 | FieldCondition.newBuilder()
139 | .setKey(field)
140 | .setMatch(Match.newBuilder().setInteger(value).build())
141 | .build())
142 | .build();
143 | }
144 |
145 | /**
146 | * Match records where the given field matches any of the given keywords.
147 | *
148 | * @param field The name of the field
149 | * @param keywords The keywords to match
150 | * @return a new instance of {@link Condition}
151 | */
152 | public static Condition matchKeywords(String field, List keywords) {
153 | return Condition.newBuilder()
154 | .setField(
155 | FieldCondition.newBuilder()
156 | .setKey(field)
157 | .setMatch(
158 | Match.newBuilder()
159 | .setKeywords(RepeatedStrings.newBuilder().addAllStrings(keywords).build())
160 | .build())
161 | .build())
162 | .build();
163 | }
164 |
165 | /**
166 | * Match records where the given field matches any of the given values.
167 | *
168 | * @param field The name of the field
169 | * @param values The values to match
170 | * @return a new instance of {@link Condition}
171 | */
172 | public static Condition matchValues(String field, List values) {
173 | return Condition.newBuilder()
174 | .setField(
175 | FieldCondition.newBuilder()
176 | .setKey(field)
177 | .setMatch(
178 | Match.newBuilder()
179 | .setIntegers(RepeatedIntegers.newBuilder().addAllIntegers(values).build())
180 | .build())
181 | .build())
182 | .build();
183 | }
184 |
185 | /**
186 | * Match records where the given field does not match any of the given keywords.
187 | *
188 | * @param field The name of the field
189 | * @param keywords The keywords not to match
190 | * @return a new instance of {@link Condition}
191 | */
192 | public static Condition matchExceptKeywords(String field, List keywords) {
193 | return Condition.newBuilder()
194 | .setField(
195 | FieldCondition.newBuilder()
196 | .setKey(field)
197 | .setMatch(
198 | Match.newBuilder()
199 | .setExceptKeywords(
200 | RepeatedStrings.newBuilder().addAllStrings(keywords).build())
201 | .build())
202 | .build())
203 | .build();
204 | }
205 |
206 | /**
207 | * Match records where the given field does not match any of the given values.
208 | *
209 | * @param field The name of the field
210 | * @param values The values not to match
211 | * @return a new instance of {@link Condition}
212 | */
213 | public static Condition matchExceptValues(String field, List values) {
214 | return Condition.newBuilder()
215 | .setField(
216 | FieldCondition.newBuilder()
217 | .setKey(field)
218 | .setMatch(
219 | Match.newBuilder()
220 | .setExceptIntegers(
221 | RepeatedIntegers.newBuilder().addAllIntegers(values).build())
222 | .build())
223 | .build())
224 | .build();
225 | }
226 |
227 | /**
228 | * Match records where the given nested field matches the given condition.
229 | *
230 | * @param field The name of the nested field.
231 | * @param condition The condition to match.
232 | * @return a new instance of {@link Condition}
233 | */
234 | public static Condition nested(String field, Condition condition) {
235 | return Condition.newBuilder()
236 | .setNested(
237 | NestedCondition.newBuilder()
238 | .setKey(field)
239 | .setFilter(Filter.newBuilder().addMust(condition).build())
240 | .build())
241 | .build();
242 | }
243 |
244 | /**
245 | * Match records where the given nested field matches the given filter.
246 | *
247 | * @param field The name of the nested field.
248 | * @param filter The filter to match.
249 | * @return a new instance of {@link Condition}
250 | */
251 | public static Condition nested(String field, Filter filter) {
252 | return Condition.newBuilder()
253 | .setNested(NestedCondition.newBuilder().setKey(field).setFilter(filter))
254 | .build();
255 | }
256 |
257 | /**
258 | * Match records where the given field matches the given range.
259 | *
260 | * @param field The name of the nested field.
261 | * @param range The range to match.
262 | * @return a new instance of {@link Condition}
263 | */
264 | public static Condition range(String field, Range range) {
265 | return Condition.newBuilder()
266 | .setField(FieldCondition.newBuilder().setKey(field).setRange(range).build())
267 | .build();
268 | }
269 |
270 | /**
271 | * Match records where the given field has values inside a circle centred at a given latitude and
272 | * longitude with a given radius.
273 | *
274 | * @param field The name of the field.
275 | * @param latitude The latitude of the center.
276 | * @param longitude The longitude of the center.
277 | * @param radius The radius in meters.
278 | * @return a new instance of {@link Condition}
279 | */
280 | public static Condition geoRadius(String field, double latitude, double longitude, float radius) {
281 | return Condition.newBuilder()
282 | .setField(
283 | FieldCondition.newBuilder()
284 | .setKey(field)
285 | .setGeoRadius(
286 | GeoRadius.newBuilder()
287 | .setCenter(GeoPoint.newBuilder().setLat(latitude).setLon(longitude).build())
288 | .setRadius(radius)
289 | .build())
290 | .build())
291 | .build();
292 | }
293 |
294 | /**
295 | * Match records where the given field has values inside a bounding box specified by the top left
296 | * and bottom right coordinates.
297 | *
298 | * @param field The name of the field.
299 | * @param topLeftLatitude The latitude of the top left point.
300 | * @param topLeftLongitude The longitude of the top left point.
301 | * @param bottomRightLatitude The latitude of the bottom right point.
302 | * @param bottomRightLongitude The longitude of the bottom right point.
303 | * @return a new instance of {@link Condition}
304 | */
305 | public static Condition geoBoundingBox(
306 | String field,
307 | double topLeftLatitude,
308 | double topLeftLongitude,
309 | double bottomRightLatitude,
310 | double bottomRightLongitude) {
311 | return Condition.newBuilder()
312 | .setField(
313 | FieldCondition.newBuilder()
314 | .setKey(field)
315 | .setGeoBoundingBox(
316 | GeoBoundingBox.newBuilder()
317 | .setTopLeft(
318 | GeoPoint.newBuilder()
319 | .setLat(topLeftLatitude)
320 | .setLon(topLeftLongitude)
321 | .build())
322 | .setBottomRight(
323 | GeoPoint.newBuilder()
324 | .setLat(bottomRightLatitude)
325 | .setLon(bottomRightLongitude)
326 | .build())
327 | .build())
328 | .build())
329 | .build();
330 | }
331 |
332 | /**
333 | * Matches records where the given field has values inside the provided polygon. A polygon always
334 | * has an exterior ring and may optionally have interior rings, which represent independent areas
335 | * or holes. When defining a ring, you must pick either a clockwise or counterclockwise ordering
336 | * for your points. The first and last point of the polygon must be the same.
337 | *
338 | * @param field The name of the field.
339 | * @param exterior The exterior ring of the polygon.
340 | * @param interiors The interior rings of the polygon.
341 | * @return a new instance of {@link Condition}
342 | */
343 | public static Condition geoPolygon(
344 | String field, GeoLineString exterior, List interiors) {
345 | GeoPolygon.Builder geoPolygonBuilder = GeoPolygon.newBuilder().setExterior(exterior);
346 |
347 | if (!interiors.isEmpty()) {
348 | geoPolygonBuilder.addAllInteriors(interiors);
349 | }
350 |
351 | return Condition.newBuilder()
352 | .setField(
353 | FieldCondition.newBuilder()
354 | .setKey(field)
355 | .setGeoPolygon(geoPolygonBuilder.build())
356 | .build())
357 | .build();
358 | }
359 |
360 | /**
361 | * Matches records where the given field has a count of values within the specified count range
362 | *
363 | * @param field The name of the field.
364 | * @param valuesCount The count range to match.
365 | * @return a new instance of {@link Condition}
366 | */
367 | public static Condition valuesCount(String field, ValuesCount valuesCount) {
368 | return Condition.newBuilder()
369 | .setField(FieldCondition.newBuilder().setKey(field).setValuesCount(valuesCount).build())
370 | .build();
371 | }
372 |
373 | /**
374 | * Nests a filter
375 | *
376 | * @param filter The filter to nest.
377 | * @return a new instance of {@link Condition}
378 | */
379 | public static Condition filter(Filter filter) {
380 | return Condition.newBuilder().setFilter(filter).build();
381 | }
382 |
383 | /**
384 | * Matches records where the given field has a datetime value within the specified range
385 | *
386 | * @param field The name of the field.
387 | * @param datetimeRange The datetime range to match.
388 | * @return a new instance of {@link Condition}
389 | */
390 | public static Condition datetimeRange(String field, DatetimeRange datetimeRange) {
391 | return Condition.newBuilder()
392 | .setField(FieldCondition.newBuilder().setKey(field).setDatetimeRange(datetimeRange).build())
393 | .build();
394 | }
395 |
396 | /**
397 | * Matches records where a value for the given vector is present.
398 | *
399 | * @param vector The name of the vector.
400 | * @return a new instance of {@link Condition}
401 | */
402 | public static Condition hasVector(String vector) {
403 | return Condition.newBuilder()
404 | .setHasVector(HasVectorCondition.newBuilder().setHasVector(vector).build())
405 | .build();
406 | }
407 | }
408 |
--------------------------------------------------------------------------------
/src/main/java/io/qdrant/client/ExpressionFactory.java:
--------------------------------------------------------------------------------
1 | package io.qdrant.client;
2 |
3 | import io.qdrant.client.grpc.Points.Condition;
4 | import io.qdrant.client.grpc.Points.DecayParamsExpression;
5 | import io.qdrant.client.grpc.Points.DivExpression;
6 | import io.qdrant.client.grpc.Points.Expression;
7 | import io.qdrant.client.grpc.Points.GeoDistance;
8 | import io.qdrant.client.grpc.Points.MultExpression;
9 | import io.qdrant.client.grpc.Points.PowExpression;
10 | import io.qdrant.client.grpc.Points.SumExpression;
11 |
12 | /** Convenience methods for constructing {@link Expression} */
13 | public final class ExpressionFactory {
14 | private ExpressionFactory() {}
15 |
16 | /**
17 | * Creates an {@link Expression} from a constant.
18 | *
19 | * @param constant The constant float value
20 | * @return a new instance of {@link Expression}
21 | */
22 | public static Expression constant(float constant) {
23 | return Expression.newBuilder().setConstant(constant).build();
24 | }
25 |
26 | /**
27 | * Creates an {@link Expression} from a variable name.
28 | *
29 | * @param variable The variable name (e.g., payload key or score reference)
30 | * @return a new instance of {@link Expression}
31 | */
32 | public static Expression variable(String variable) {
33 | return Expression.newBuilder().setVariable(variable).build();
34 | }
35 |
36 | /**
37 | * Creates an {@link Expression} from a {@link Condition}.
38 | *
39 | * @param condition The condition to evaluate
40 | * @return a new instance of {@link Expression}
41 | */
42 | public static Expression condition(Condition condition) {
43 | return Expression.newBuilder().setCondition(condition).build();
44 | }
45 |
46 | /**
47 | * Creates an {@link Expression} from a {@link GeoDistance}.
48 | *
49 | * @param geoDistance The geo distance object
50 | * @return a new instance of {@link Expression}
51 | */
52 | public static Expression geoDistance(GeoDistance geoDistance) {
53 | return Expression.newBuilder().setGeoDistance(geoDistance).build();
54 | }
55 |
56 | /**
57 | * Creates an {@link Expression} from a date-time constant string.
58 | *
59 | * @param datetime The date-time string
60 | * @return a new instance of {@link Expression}
61 | */
62 | public static Expression datetime(String datetime) {
63 | return Expression.newBuilder().setDatetime(datetime).build();
64 | }
65 |
66 | /**
67 | * Creates an {@link Expression} from a payload key referencing date-time values.
68 | *
69 | * @param datetimeKey The payload key containing date-time values
70 | * @return a new instance of {@link Expression}
71 | */
72 | public static Expression datetimeKey(String datetimeKey) {
73 | return Expression.newBuilder().setDatetimeKey(datetimeKey).build();
74 | }
75 |
76 | /**
77 | * Creates an {@link Expression} that multiplies values.
78 | *
79 | * @param mult The multiplication expression
80 | * @return a new instance of {@link Expression}
81 | */
82 | public static Expression mult(MultExpression mult) {
83 | return Expression.newBuilder().setMult(mult).build();
84 | }
85 |
86 | /**
87 | * Creates an {@link Expression} that sums values.
88 | *
89 | * @param sum The summation expression
90 | * @return a new instance of {@link Expression}
91 | */
92 | public static Expression sum(SumExpression sum) {
93 | return Expression.newBuilder().setSum(sum).build();
94 | }
95 |
96 | /**
97 | * Creates an {@link Expression} that divides values.
98 | *
99 | * @param div The division expression
100 | * @return a new instance of {@link Expression}
101 | */
102 | public static Expression div(DivExpression div) {
103 | return Expression.newBuilder().setDiv(div).build();
104 | }
105 |
106 | /**
107 | * Creates a negated {@link Expression}.
108 | *
109 | * @param expr The expression to negate
110 | * @return a new instance of {@link Expression}
111 | */
112 | public static Expression negate(Expression expr) {
113 | return Expression.newBuilder().setNeg(expr).build();
114 | }
115 |
116 | /**
117 | * Creates an {@link Expression} representing absolute value.
118 | *
119 | * @param expr The expression to wrap with abs()
120 | * @return a new instance of {@link Expression}
121 | */
122 | public static Expression abs(Expression expr) {
123 | return Expression.newBuilder().setAbs(expr).build();
124 | }
125 |
126 | /**
127 | * Creates an {@link Expression} representing square root.
128 | *
129 | * @param expr The expression to apply sqrt() to
130 | * @return a new instance of {@link Expression}
131 | */
132 | public static Expression sqrt(Expression expr) {
133 | return Expression.newBuilder().setSqrt(expr).build();
134 | }
135 |
136 | /**
137 | * Creates an {@link Expression} from a {@link PowExpression}.
138 | *
139 | * @param pow The power expression (base and exponent)
140 | * @return a new instance of {@link Expression}
141 | */
142 | public static Expression pow(PowExpression pow) {
143 | return Expression.newBuilder().setPow(pow).build();
144 | }
145 |
146 | /**
147 | * Creates an {@link Expression} representing exponential.
148 | *
149 | * @param expr The expression to apply exponential to
150 | * @return a new instance of {@link Expression}
151 | */
152 | public static Expression exp(Expression expr) {
153 | return Expression.newBuilder().setExp(expr).build();
154 | }
155 |
156 | /**
157 | * Creates an {@link Expression} representing base-10 logarithm.
158 | *
159 | * @param expr The expression to apply log10() to
160 | * @return a new instance of {@link Expression}
161 | */
162 | public static Expression log10(Expression expr) {
163 | return Expression.newBuilder().setLog10(expr).build();
164 | }
165 |
166 | /**
167 | * Creates an {@link Expression} representing natural logarithm.
168 | *
169 | * @param expr The expression to apply natural log to
170 | * @return a new instance of {@link Expression}
171 | */
172 | public static Expression ln(Expression expr) {
173 | return Expression.newBuilder().setLn(expr).build();
174 | }
175 |
176 | /**
177 | * Creates an {@link Expression} representing exponential decay.
178 | *
179 | * @param decay The decay parameters
180 | * @return a new instance of {@link Expression}
181 | */
182 | public static Expression expDecay(DecayParamsExpression decay) {
183 | return Expression.newBuilder().setExpDecay(decay).build();
184 | }
185 |
186 | /**
187 | * Creates an {@link Expression} representing Gaussian decay.
188 | *
189 | * @param decay The decay parameters
190 | * @return a new instance of {@link Expression}
191 | */
192 | public static Expression gaussDecay(DecayParamsExpression decay) {
193 | return Expression.newBuilder().setGaussDecay(decay).build();
194 | }
195 |
196 | /**
197 | * Creates an {@link Expression} representing linear decay.
198 | *
199 | * @param decay The decay parameters
200 | * @return a new instance of {@link Expression}
201 | */
202 | public static Expression linDecay(DecayParamsExpression decay) {
203 | return Expression.newBuilder().setLinDecay(decay).build();
204 | }
205 | }
206 |
--------------------------------------------------------------------------------
/src/main/java/io/qdrant/client/PointIdFactory.java:
--------------------------------------------------------------------------------
1 | package io.qdrant.client;
2 |
3 | import io.qdrant.client.grpc.Points.PointId;
4 | import java.util.UUID;
5 |
6 | /** Convenience methods for constructing {@link PointId} */
7 | public final class PointIdFactory {
8 | private PointIdFactory() {}
9 |
10 | /**
11 | * Creates a point id from a {@link long}
12 | *
13 | * @param id The id
14 | * @return a new instance of {@link PointId}
15 | */
16 | public static PointId id(long id) {
17 | return PointId.newBuilder().setNum(id).build();
18 | }
19 |
20 | /**
21 | * Creates a point id from a {@link UUID}
22 | *
23 | * @param id The id
24 | * @return a new instance of {@link PointId}
25 | */
26 | public static PointId id(UUID id) {
27 | return PointId.newBuilder().setUuid(id.toString()).build();
28 | }
29 | }
30 |
--------------------------------------------------------------------------------
/src/main/java/io/qdrant/client/QdrantException.java:
--------------------------------------------------------------------------------
1 | package io.qdrant.client;
2 |
3 | /** An exception when interacting with qdrant */
4 | public class QdrantException extends RuntimeException {
5 | /**
6 | * Instantiates a new instance of {@link QdrantException}
7 | *
8 | * @param message The exception message
9 | */
10 | public QdrantException(String message) {
11 | super(message);
12 | }
13 | }
14 |
--------------------------------------------------------------------------------
/src/main/java/io/qdrant/client/QdrantGrpcClient.java:
--------------------------------------------------------------------------------
1 | package io.qdrant.client;
2 |
3 | import io.grpc.CallCredentials;
4 | import io.grpc.Deadline;
5 | import io.grpc.ManagedChannel;
6 | import io.grpc.ManagedChannelBuilder;
7 | import io.qdrant.client.grpc.*;
8 | import io.qdrant.client.grpc.CollectionsGrpc.CollectionsFutureStub;
9 | import io.qdrant.client.grpc.PointsGrpc.PointsFutureStub;
10 | import io.qdrant.client.grpc.QdrantGrpc.QdrantFutureStub;
11 | import io.qdrant.client.grpc.SnapshotsGrpc.SnapshotsFutureStub;
12 | import java.time.Duration;
13 | import java.util.concurrent.TimeUnit;
14 | import javax.annotation.Nullable;
15 | import org.slf4j.Logger;
16 | import org.slf4j.LoggerFactory;
17 |
18 | /** Low-level gRPC client for qdrant vector database. */
19 | public class QdrantGrpcClient implements AutoCloseable {
20 | private static final Logger logger = LoggerFactory.getLogger(QdrantGrpcClient.class);
21 | @Nullable private final CallCredentials callCredentials;
22 | private final ManagedChannel channel;
23 | private final boolean shutdownChannelOnClose;
24 | @Nullable private final Duration timeout;
25 |
26 | QdrantGrpcClient(
27 | ManagedChannel channel,
28 | boolean shutdownChannelOnClose,
29 | @Nullable CallCredentials callCredentials,
30 | @Nullable Duration timeout) {
31 | this.callCredentials = callCredentials;
32 | this.channel = channel;
33 | this.shutdownChannelOnClose = shutdownChannelOnClose;
34 | this.timeout = timeout;
35 | }
36 |
37 | /**
38 | * Creates a new builder to build a client.
39 | *
40 | * @param channel The channel for communication. This channel is not shutdown by the client and
41 | * must be managed by the caller.
42 | * @return a new instance of {@link Builder}
43 | */
44 | public static Builder newBuilder(ManagedChannel channel) {
45 | return new Builder(channel, false, true);
46 | }
47 |
48 | /**
49 | * Creates a new builder to build a client.
50 | *
51 | * @param channel The channel for communication.
52 | * @param shutdownChannelOnClose Whether the channel is shutdown on client close.
53 | * @return a new instance of {@link Builder}
54 | */
55 | public static Builder newBuilder(ManagedChannel channel, boolean shutdownChannelOnClose) {
56 | return new Builder(channel, shutdownChannelOnClose, true);
57 | }
58 |
59 | /**
60 | * Creates a new builder to build a client.
61 | *
62 | * @param channel The channel for communication.
63 | * @param shutdownChannelOnClose Whether the channel is shutdown on client close.
64 | * @param checkCompatibility Whether to check compatibility between client's and server's
65 | * versions.
66 | * @return a new instance of {@link Builder}
67 | */
68 | public static Builder newBuilder(
69 | ManagedChannel channel, boolean shutdownChannelOnClose, boolean checkCompatibility) {
70 | return new Builder(channel, shutdownChannelOnClose, checkCompatibility);
71 | }
72 |
73 | /**
74 | * Creates a new builder to build a client.
75 | *
76 | * @param host The host to connect to. The default gRPC port 6334 is used.
77 | * @return a new instance of {@link Builder}
78 | */
79 | public static Builder newBuilder(String host) {
80 | return new Builder(host, 6334, true, true);
81 | }
82 |
83 | /**
84 | * Creates a new builder to build a client. The client uses Transport Layer Security by default.
85 | *
86 | * @param host The host to connect to.
87 | * @param port The port to connect to.
88 | * @return a new instance of {@link Builder}
89 | */
90 | public static Builder newBuilder(String host, int port) {
91 | return new Builder(host, port, true, true);
92 | }
93 |
94 | /**
95 | * Creates a new builder to build a client.
96 | *
97 | * @param host The host to connect to.
98 | * @param port The port to connect to.
99 | * @param useTransportLayerSecurity Whether the client uses Transport Layer Security (TLS) to
100 | * secure communications. Running without TLS should only be used for testing purposes.
101 | * @return a new instance of {@link Builder}
102 | */
103 | public static Builder newBuilder(String host, int port, boolean useTransportLayerSecurity) {
104 | return new Builder(host, port, useTransportLayerSecurity, true);
105 | }
106 |
107 | /**
108 | * Creates a new builder to build a client.
109 | *
110 | * @param host The host to connect to.
111 | * @param port The port to connect to.
112 | * @param useTransportLayerSecurity Whether the client uses Transport Layer Security (TLS) to
113 | * secure communications. Running without TLS should only be used for testing purposes.
114 | * @param checkCompatibility Whether to check compatibility between client's and server's
115 | * versions.
116 | * @return a new instance of {@link Builder}
117 | */
118 | public static Builder newBuilder(
119 | String host, int port, boolean useTransportLayerSecurity, boolean checkCompatibility) {
120 | return new Builder(host, port, useTransportLayerSecurity, checkCompatibility);
121 | }
122 |
123 | /**
124 | * Gets the channel
125 | *
126 | * @return the channel
127 | */
128 | public ManagedChannel channel() {
129 | return channel;
130 | }
131 |
132 | /**
133 | * Gets the client for qdrant services
134 | *
135 | * @return a new instance of {@link QdrantFutureStub}
136 | */
137 | public QdrantGrpc.QdrantFutureStub qdrant() {
138 | return QdrantGrpc.newFutureStub(channel)
139 | .withCallCredentials(callCredentials)
140 | .withDeadline(
141 | timeout != null ? Deadline.after(timeout.toMillis(), TimeUnit.MILLISECONDS) : null);
142 | }
143 |
144 | /**
145 | * Gets the client for points
146 | *
147 | * @return a new instance of {@link PointsFutureStub}
148 | */
149 | public PointsFutureStub points() {
150 | return PointsGrpc.newFutureStub(channel)
151 | .withCallCredentials(callCredentials)
152 | .withDeadline(
153 | timeout != null ? Deadline.after(timeout.toMillis(), TimeUnit.MILLISECONDS) : null);
154 | }
155 |
156 | /**
157 | * Gets the client for collections
158 | *
159 | * @return a new instance of {@link CollectionsFutureStub}
160 | */
161 | public CollectionsFutureStub collections() {
162 | return CollectionsGrpc.newFutureStub(channel)
163 | .withCallCredentials(callCredentials)
164 | .withDeadline(
165 | timeout != null ? Deadline.after(timeout.toMillis(), TimeUnit.MILLISECONDS) : null);
166 | }
167 |
168 | /**
169 | * Gets the client for snapshots
170 | *
171 | * @return a new instance of {@link SnapshotsFutureStub}
172 | */
173 | public SnapshotsFutureStub snapshots() {
174 | return SnapshotsGrpc.newFutureStub(channel)
175 | .withCallCredentials(callCredentials)
176 | .withDeadline(
177 | timeout != null ? Deadline.after(timeout.toMillis(), TimeUnit.MILLISECONDS) : null);
178 | }
179 |
180 | @Override
181 | public void close() {
182 | if (shutdownChannelOnClose && !channel.isShutdown() && !channel.isTerminated()) {
183 | try {
184 | channel.shutdown().awaitTermination(5, TimeUnit.SECONDS);
185 | } catch (InterruptedException e) {
186 | logger.warn("exception thrown when shutting down channel", e);
187 | }
188 | }
189 | }
190 |
191 | /** builder for {@link QdrantGrpcClient} */
192 | public static class Builder {
193 | private final ManagedChannel channel;
194 | private final boolean shutdownChannelOnClose;
195 | @Nullable private CallCredentials callCredentials;
196 | @Nullable private Duration timeout;
197 |
198 | Builder(ManagedChannel channel, boolean shutdownChannelOnClose, boolean checkCompatibility) {
199 | this.channel = channel;
200 | this.shutdownChannelOnClose = shutdownChannelOnClose;
201 | String clientVersion = Builder.class.getPackage().getImplementationVersion();
202 | if (checkCompatibility) {
203 | checkVersionsCompatibility(clientVersion);
204 | }
205 | }
206 |
207 | Builder(String host, int port, boolean useTransportLayerSecurity, boolean checkCompatibility) {
208 | String clientVersion = Builder.class.getPackage().getImplementationVersion();
209 | String javaVersion = System.getProperty("java.version");
210 | String userAgent = "java-client/" + clientVersion + " java/" + javaVersion;
211 | this.channel = createChannel(host, port, useTransportLayerSecurity, userAgent);
212 | this.shutdownChannelOnClose = true;
213 | if (checkCompatibility) {
214 | checkVersionsCompatibility(clientVersion);
215 | }
216 | }
217 |
218 | /**
219 | * Sets the API key to use for authentication
220 | *
221 | * @param apiKey The API key to use.
222 | * @return this
223 | */
224 | public Builder withApiKey(String apiKey) {
225 | this.callCredentials = new ApiKeyCredentials(apiKey);
226 | return this;
227 | }
228 |
229 | /**
230 | * Sets a default timeout for all requests.
231 | *
232 | * @param timeout The timeout.
233 | * @return this
234 | */
235 | public Builder withTimeout(@Nullable Duration timeout) {
236 | this.timeout = timeout;
237 | return this;
238 | }
239 |
240 | /**
241 | * Sets the credential data that will be propagated to the server via request metadata for each
242 | * RPC.
243 | *
244 | * @param callCredentials The call credentials to use.
245 | * @return this
246 | */
247 | public Builder withCallCredentials(@Nullable CallCredentials callCredentials) {
248 | this.callCredentials = callCredentials;
249 | return this;
250 | }
251 |
252 | /**
253 | * Builds a new instance of {@link QdrantGrpcClient}
254 | *
255 | * @return a new instance of {@link QdrantGrpcClient}
256 | */
257 | public QdrantGrpcClient build() {
258 | return new QdrantGrpcClient(channel, shutdownChannelOnClose, callCredentials, timeout);
259 | }
260 |
261 | private static ManagedChannel createChannel(
262 | String host, int port, boolean useTransportLayerSecurity, String userAgent) {
263 | ManagedChannelBuilder> channelBuilder = ManagedChannelBuilder.forAddress(host, port);
264 |
265 | if (useTransportLayerSecurity) {
266 | channelBuilder.useTransportSecurity();
267 | } else {
268 | channelBuilder.usePlaintext();
269 | }
270 |
271 | channelBuilder.userAgent(userAgent);
272 |
273 | return channelBuilder.build();
274 | }
275 |
276 | private void checkVersionsCompatibility(String clientVersion) {
277 | try {
278 | String serverVersion =
279 | QdrantGrpc.newBlockingStub(this.channel)
280 | .healthCheck(QdrantOuterClass.HealthCheckRequest.getDefaultInstance())
281 | .getVersion();
282 | if (!VersionsCompatibilityChecker.isCompatible(clientVersion, serverVersion)) {
283 | String logMessage =
284 | "Qdrant client version "
285 | + clientVersion
286 | + " is incompatible with server version "
287 | + serverVersion
288 | + ". Major versions should match and minor version difference must not exceed 1. "
289 | + "Set checkCompatibility=false to skip version check.";
290 | logger.warn(logMessage);
291 | }
292 | } catch (Exception e) {
293 | logger.warn(
294 | "Failed to obtain server version. Unable to check client-server compatibility. Set checkCompatibility=false to skip version check.");
295 | }
296 | }
297 | }
298 | }
299 |
--------------------------------------------------------------------------------
/src/main/java/io/qdrant/client/QueryFactory.java:
--------------------------------------------------------------------------------
1 | package io.qdrant.client;
2 |
3 | import static io.qdrant.client.VectorInputFactory.multiVectorInput;
4 | import static io.qdrant.client.VectorInputFactory.vectorInput;
5 |
6 | import io.qdrant.client.grpc.Points.ContextInput;
7 | import io.qdrant.client.grpc.Points.DiscoverInput;
8 | import io.qdrant.client.grpc.Points.Formula;
9 | import io.qdrant.client.grpc.Points.Fusion;
10 | import io.qdrant.client.grpc.Points.OrderBy;
11 | import io.qdrant.client.grpc.Points.PointId;
12 | import io.qdrant.client.grpc.Points.Query;
13 | import io.qdrant.client.grpc.Points.RecommendInput;
14 | import io.qdrant.client.grpc.Points.Sample;
15 | import io.qdrant.client.grpc.Points.VectorInput;
16 | import java.util.List;
17 | import java.util.UUID;
18 |
19 | /** Convenience methods for constructing {@link Query} */
20 | public final class QueryFactory {
21 | private QueryFactory() {}
22 |
23 | /**
24 | * Creates a {@link Query} for recommendation.
25 | *
26 | * @param input An instance of {@link RecommendInput}
27 | * @return a new instance of {@link Query}
28 | */
29 | public static Query recommend(RecommendInput input) {
30 | return Query.newBuilder().setRecommend(input).build();
31 | }
32 |
33 | /**
34 | * Creates a {@link Query} for discovery.
35 | *
36 | * @param input An instance of {@link DiscoverInput}
37 | * @return a new instance of {@link Query}
38 | */
39 | public static Query discover(DiscoverInput input) {
40 | return Query.newBuilder().setDiscover(input).build();
41 | }
42 |
43 | /**
44 | * Creates a {@link Query} for context search.
45 | *
46 | * @param input An instance of {@link ContextInput}
47 | * @return a new instance of {@link Query}
48 | */
49 | public static Query context(ContextInput input) {
50 | return Query.newBuilder().setContext(input).build();
51 | }
52 |
53 | /**
54 | * Creates a {@link Query} for pre-fetch results fusion.
55 | *
56 | * @param fusion An instance of {@link Fusion}
57 | * @return a new instance of {@link Query}
58 | */
59 | public static Query fusion(Fusion fusion) {
60 | return Query.newBuilder().setFusion(fusion).build();
61 | }
62 |
63 | /**
64 | * Creates a {@link Query} to order points by a payload field.
65 | *
66 | * @param key Name of the payload field to order by
67 | * @return a new instance of {@link Query}
68 | */
69 | public static Query orderBy(String key) {
70 | OrderBy orderBy = OrderBy.newBuilder().setKey(key).build();
71 | return Query.newBuilder().setOrderBy(orderBy).build();
72 | }
73 |
74 | /**
75 | * Creates a {@link Query} to order points by a payload field.
76 | *
77 | * @param orderBy An instance of {@link OrderBy}
78 | * @return a new instance of {@link Query}
79 | */
80 | public static Query orderBy(OrderBy orderBy) {
81 | return Query.newBuilder().setOrderBy(orderBy).build();
82 | }
83 |
84 | /**
85 | * Creates a {@link Query} for score boosting using an arbitrary formula.
86 | *
87 | * @param formula An instance of {@link Formula}
88 | * @return a new instance of {@link Query}
89 | */
90 | public static Query formula(Formula formula) {
91 | return Query.newBuilder().setFormula(formula).build();
92 | }
93 |
94 | // region Nearest search queries
95 |
96 | /**
97 | * Creates a {@link Query} for nearest search.
98 | *
99 | * @param input An instance of {@link VectorInput}
100 | * @return a new instance of {@link Query}
101 | */
102 | public static Query nearest(VectorInput input) {
103 | return Query.newBuilder().setNearest(input).build();
104 | }
105 |
106 | /**
107 | * Creates a {@link Query} from a list of floats
108 | *
109 | * @param values A map of vector names to values
110 | * @return A new instance of {@link Query}
111 | */
112 | public static Query nearest(List values) {
113 | return Query.newBuilder().setNearest(vectorInput(values)).build();
114 | }
115 |
116 | /**
117 | * Creates a {@link Query} from a list of floats
118 | *
119 | * @param values A list of values
120 | * @return A new instance of {@link Query}
121 | */
122 | public static Query nearest(float... values) {
123 | return Query.newBuilder().setNearest(vectorInput(values)).build();
124 | }
125 |
126 | /**
127 | * Creates a {@link Query} from a list of floats and integers as indices
128 | *
129 | * @param values The list of floats representing the vector.
130 | * @param indices The list of integers representing the indices.
131 | * @return A new instance of {@link Query}
132 | */
133 | public static Query nearest(List values, List indices) {
134 | return Query.newBuilder().setNearest(vectorInput(values, indices)).build();
135 | }
136 |
137 | /**
138 | * Creates a {@link Query} from a nested array of floats representing a multi vector
139 | *
140 | * @param vectors The nested array of floats.
141 | * @return A new instance of {@link Query}
142 | */
143 | public static Query nearest(float[][] vectors) {
144 | return Query.newBuilder().setNearest(multiVectorInput(vectors)).build();
145 | }
146 |
147 | /**
148 | * Creates a {@link Query} from a {@link long}
149 | *
150 | * @param id The point id
151 | * @return a new instance of {@link Query}
152 | */
153 | public static Query nearest(long id) {
154 | return Query.newBuilder().setNearest(vectorInput(id)).build();
155 | }
156 |
157 | /**
158 | * Creates a {@link Query} from a {@link UUID}
159 | *
160 | * @param id The pint id
161 | * @return a new instance of {@link Query}
162 | */
163 | public static Query nearest(UUID id) {
164 | return Query.newBuilder().setNearest(vectorInput(id)).build();
165 | }
166 |
167 | /**
168 | * Creates a {@link Query} from a {@link PointId}
169 | *
170 | * @param id The pint id
171 | * @return a new instance of {@link Query}
172 | */
173 | public static Query nearest(PointId id) {
174 | return Query.newBuilder().setNearest(vectorInput(id)).build();
175 | }
176 |
177 | /**
178 | * Creates a {@link Query} from a nested list of floats representing a multi vector
179 | *
180 | * @param vectors The nested list of floats.
181 | * @return A new instance of {@link Query}
182 | */
183 | public static Query nearestMultiVector(List> vectors) {
184 | return Query.newBuilder().setNearest(multiVectorInput(vectors)).build();
185 | }
186 |
187 | /**
188 | * Creates a {@link Query} for sampling.
189 | *
190 | * @param sample An instance of {@link Sample}
191 | * @return A new instance of {@link Query}
192 | */
193 | public static Query sample(Sample sample) {
194 | return Query.newBuilder().setSample(sample).build();
195 | }
196 |
197 | // endregion
198 | }
199 |
--------------------------------------------------------------------------------
/src/main/java/io/qdrant/client/ShardKeyFactory.java:
--------------------------------------------------------------------------------
1 | package io.qdrant.client;
2 |
3 | import io.qdrant.client.grpc.Collections.ShardKey;
4 |
5 | /** Convenience methods for constructing {@link ShardKey} */
6 | public final class ShardKeyFactory {
7 | private ShardKeyFactory() {}
8 |
9 | /**
10 | * Creates a {@link ShardKey} based on a keyword.
11 | *
12 | * @param keyword The keyword to create the shard key from
13 | * @return The {@link ShardKey} object
14 | */
15 | public static ShardKey shardKey(String keyword) {
16 | return ShardKey.newBuilder().setKeyword(keyword).build();
17 | }
18 |
19 | /**
20 | * Creates a {@link ShardKey} based on a number.
21 | *
22 | * @param number The number to create the shard key from
23 | * @return The {@link ShardKey} object
24 | */
25 | public static ShardKey shardKey(long number) {
26 | return ShardKey.newBuilder().setNumber(number).build();
27 | }
28 | }
29 |
--------------------------------------------------------------------------------
/src/main/java/io/qdrant/client/ShardKeySelectorFactory.java:
--------------------------------------------------------------------------------
1 | package io.qdrant.client;
2 |
3 | import static io.qdrant.client.ShardKeyFactory.shardKey;
4 |
5 | import io.qdrant.client.grpc.Collections.ShardKey;
6 | import io.qdrant.client.grpc.Points.ShardKeySelector;
7 | import java.util.Arrays;
8 |
9 | /** Convenience methods for constructing {@link ShardKeySelector} */
10 | public class ShardKeySelectorFactory {
11 | private ShardKeySelectorFactory() {}
12 |
13 | /**
14 | * Creates a {@link ShardKeySelector} with the given shard keys.
15 | *
16 | * @param shardKeys The shard keys to include in the selector.
17 | * @return The created {@link ShardKeySelector} object.
18 | */
19 | public static ShardKeySelector shardKeySelector(ShardKey... shardKeys) {
20 | return ShardKeySelector.newBuilder().addAllShardKeys(Arrays.asList(shardKeys)).build();
21 | }
22 |
23 | /**
24 | * Creates a {@link ShardKeySelector} with the given shard key keywords.
25 | *
26 | * @param keywords The shard key keywords to include in the selector.
27 | * @return The created {@link ShardKeySelector} object.
28 | */
29 | public static ShardKeySelector shardKeySelector(String... keywords) {
30 | ShardKeySelector.Builder builder = ShardKeySelector.newBuilder();
31 | for (String keyword : keywords) {
32 | builder.addShardKeys(shardKey(keyword));
33 | }
34 | return builder.build();
35 | }
36 |
37 | /**
38 | * Creates a {@link ShardKeySelector} with the given shard key numbers.
39 | *
40 | * @param numbers The shard key numbers to include in the selector.
41 | * @return The created {@link ShardKeySelector} object.
42 | */
43 | public static ShardKeySelector shardKeySelector(long... numbers) {
44 | ShardKeySelector.Builder builder = ShardKeySelector.newBuilder();
45 | for (long number : numbers) {
46 | builder.addShardKeys(shardKey(number));
47 | }
48 | return builder.build();
49 | }
50 | }
51 |
--------------------------------------------------------------------------------
/src/main/java/io/qdrant/client/StartFromFactory.java:
--------------------------------------------------------------------------------
1 | package io.qdrant.client;
2 |
3 | import com.google.protobuf.Timestamp;
4 | import io.qdrant.client.grpc.Points.StartFrom;
5 | import java.time.Instant;
6 |
7 | /** Convenience methods for constructing {@link StartFrom} */
8 | public final class StartFromFactory {
9 | private StartFromFactory() {}
10 |
11 | /**
12 | * Creates a {@link StartFrom} value from a {@link float}
13 | *
14 | * @param value The value
15 | * @return a new instance of {@link StartFrom}
16 | */
17 | public static StartFrom startFrom(float value) {
18 | return StartFrom.newBuilder().setFloat(value).build();
19 | }
20 |
21 | /**
22 | * Creates a {@link StartFrom} value from a {@link int}
23 | *
24 | * @param value The value
25 | * @return a new instance of {@link StartFrom}
26 | */
27 | public static StartFrom startFrom(int value) {
28 | return StartFrom.newBuilder().setInteger(value).build();
29 | }
30 |
31 | /**
32 | * Creates a {@link StartFrom} value from a {@link String} timestamp
33 | *
34 | * @param value The value
35 | * @return a new instance of {@link StartFrom}
36 | */
37 | public static StartFrom startFrom(String value) {
38 | return StartFrom.newBuilder().setDatetime(value).build();
39 | }
40 |
41 | /**
42 | * Creates a {@link StartFrom} value from a {@link Timestamp}
43 | *
44 | * @param value The value
45 | * @return a new instance of {@link StartFrom}
46 | */
47 | public static StartFrom startFrom(Timestamp value) {
48 | return StartFrom.newBuilder().setTimestamp(value).build();
49 | }
50 |
51 | /**
52 | * Creates a {@link StartFrom} value from a {@link Timestamp}
53 | *
54 | * @param value The value
55 | * @return a new instance of {@link StartFrom}
56 | */
57 | public static StartFrom startFrom(Instant value) {
58 | return StartFrom.newBuilder()
59 | .setTimestamp(Timestamp.newBuilder().setSeconds(value.getEpochSecond()))
60 | .build();
61 | }
62 | }
63 |
--------------------------------------------------------------------------------
/src/main/java/io/qdrant/client/TargetVectorFactory.java:
--------------------------------------------------------------------------------
1 | package io.qdrant.client;
2 |
3 | import io.qdrant.client.grpc.Points.PointId;
4 | import io.qdrant.client.grpc.Points.TargetVector;
5 | import io.qdrant.client.grpc.Points.Vector;
6 | import io.qdrant.client.grpc.Points.VectorExample;
7 |
8 | /** Convenience methods for constructing {@link TargetVector} */
9 | public class TargetVectorFactory {
10 | private TargetVectorFactory() {}
11 |
12 | /**
13 | * Creates a TargetVector from a point ID
14 | *
15 | * @param id The point ID to use
16 | * @return A new instance of {@link TargetVector}
17 | */
18 | public static TargetVector targetVector(PointId id) {
19 | return TargetVector.newBuilder().setSingle(VectorExample.newBuilder().setId(id)).build();
20 | }
21 |
22 | /**
23 | * Creates a TargetVector from a Vector
24 | *
25 | * @param vector The Vector value to use
26 | * @return A new instance of {@link TargetVector}
27 | */
28 | public static TargetVector targetVector(Vector vector) {
29 | return TargetVector.newBuilder()
30 | .setSingle(VectorExample.newBuilder().setVector(vector))
31 | .build();
32 | }
33 | }
34 |
--------------------------------------------------------------------------------
/src/main/java/io/qdrant/client/ValueFactory.java:
--------------------------------------------------------------------------------
1 | package io.qdrant.client;
2 |
3 | import io.qdrant.client.grpc.JsonWithInt.ListValue;
4 | import io.qdrant.client.grpc.JsonWithInt.NullValue;
5 | import io.qdrant.client.grpc.JsonWithInt.Struct;
6 | import io.qdrant.client.grpc.JsonWithInt.Value;
7 | import java.util.List;
8 | import java.util.Map;
9 |
10 | /** Convenience methods for constructing {@link Value} */
11 | public final class ValueFactory {
12 | private ValueFactory() {}
13 |
14 | /**
15 | * Creates a value from a {@link String}
16 | *
17 | * @param value The value
18 | * @return a new instance of {@link io.qdrant.client.grpc.JsonWithInt.Value}
19 | */
20 | public static Value value(String value) {
21 | return Value.newBuilder().setStringValue(value).build();
22 | }
23 |
24 | /**
25 | * Creates a value from a {@link long}
26 | *
27 | * @param value The value
28 | * @return a new instance of {@link io.qdrant.client.grpc.JsonWithInt.Value}
29 | */
30 | public static Value value(long value) {
31 | return Value.newBuilder().setIntegerValue(value).build();
32 | }
33 |
34 | /**
35 | * Creates a value from a {@link double}
36 | *
37 | * @param value The value
38 | * @return a new instance of {@link io.qdrant.client.grpc.JsonWithInt.Value}
39 | */
40 | public static Value value(double value) {
41 | return Value.newBuilder().setDoubleValue(value).build();
42 | }
43 |
44 | /**
45 | * Creates a value from a {@link boolean}
46 | *
47 | * @param value The value
48 | * @return a new instance of {@link io.qdrant.client.grpc.JsonWithInt.Value}
49 | */
50 | public static Value value(boolean value) {
51 | return Value.newBuilder().setBoolValue(value).build();
52 | }
53 |
54 | /**
55 | * Creates a null value
56 | *
57 | * @return a new instance of {@link io.qdrant.client.grpc.JsonWithInt.Value}
58 | */
59 | public static Value nullValue() {
60 | return Value.newBuilder().setNullValue(NullValue.NULL_VALUE).build();
61 | }
62 |
63 | /**
64 | * Creates a value from a list of values
65 | *
66 | * @param values The list of values
67 | * @return a new instance of {@link io.qdrant.client.grpc.JsonWithInt.Value}
68 | */
69 | public static Value list(List values) {
70 | return Value.newBuilder()
71 | .setListValue(ListValue.newBuilder().addAllValues(values).build())
72 | .build();
73 | }
74 |
75 | /**
76 | * Creates a value from a list of values. Same as {@link #list(List)}
77 | *
78 | * @param values The list of values
79 | * @return a new instance of {@link io.qdrant.client.grpc.JsonWithInt.Value}
80 | */
81 | public static Value value(List values) {
82 | return list(values);
83 | }
84 |
85 | /**
86 | * Creates a value from a map of nested values
87 | *
88 | * @param values The map of values
89 | * @return a new instance of {@link io.qdrant.client.grpc.JsonWithInt.Value}
90 | */
91 | public static Value value(Map values) {
92 | return Value.newBuilder()
93 | .setStructValue(Struct.newBuilder().putAllFields(values).build())
94 | .build();
95 | }
96 | }
97 |
--------------------------------------------------------------------------------
/src/main/java/io/qdrant/client/VectorFactory.java:
--------------------------------------------------------------------------------
1 | package io.qdrant.client;
2 |
3 | import com.google.common.primitives.Floats;
4 | import io.qdrant.client.grpc.Points.SparseIndices;
5 | import io.qdrant.client.grpc.Points.Vector;
6 | import java.util.ArrayList;
7 | import java.util.List;
8 | import java.util.stream.Collectors;
9 |
10 | /** Convenience methods for constructing {@link Vector} */
11 | public final class VectorFactory {
12 | private VectorFactory() {}
13 |
14 | /**
15 | * Creates a vector from a list of floats
16 | *
17 | * @param values A map of vector names to values
18 | * @return A new instance of {@link Vector}
19 | */
20 | public static Vector vector(List values) {
21 | return Vector.newBuilder().addAllData(values).build();
22 | }
23 |
24 | /**
25 | * Creates a vector from a list of floats
26 | *
27 | * @param values A list of values
28 | * @return A new instance of {@link Vector}
29 | */
30 | public static Vector vector(float... values) {
31 | return Vector.newBuilder().addAllData(Floats.asList(values)).build();
32 | }
33 |
34 | /**
35 | * Creates a sparse vector from a list of floats and integers as indices
36 | *
37 | * @param vector The list of floats representing the vector.
38 | * @param indices The list of integers representing the indices.
39 | * @return A new instance of {@link Vector}
40 | */
41 | public static Vector vector(List vector, List indices) {
42 | return Vector.newBuilder()
43 | .addAllData(vector)
44 | .setIndices(SparseIndices.newBuilder().addAllData(indices).build())
45 | .build();
46 | }
47 |
48 | /**
49 | * Creates a multi vector from a nested list of floats
50 | *
51 | * @param vectors The nested list of floats representing the multi vector.
52 | * @return A new instance of {@link Vector}
53 | */
54 | public static Vector multiVector(List> vectors) {
55 | int vectorSize = vectors.size();
56 | List flatVector = vectors.stream().flatMap(List::stream).collect(Collectors.toList());
57 |
58 | return Vector.newBuilder().addAllData(flatVector).setVectorsCount(vectorSize).build();
59 | }
60 |
61 | /**
62 | * Creates a multi vector from a nested array of floats
63 | *
64 | * @param vectors The nested array of floats representing the multi vector.
65 | * @return A new instance of {@link Vector}
66 | */
67 | public static Vector multiVector(float[][] vectors) {
68 | int vectorSize = vectors.length;
69 |
70 | List flatVector = new ArrayList<>();
71 | for (float[] vector : vectors) {
72 | for (float value : vector) {
73 | flatVector.add(value);
74 | }
75 | }
76 |
77 | return Vector.newBuilder().addAllData(flatVector).setVectorsCount(vectorSize).build();
78 | }
79 | }
80 |
--------------------------------------------------------------------------------
/src/main/java/io/qdrant/client/VectorInputFactory.java:
--------------------------------------------------------------------------------
1 | package io.qdrant.client;
2 |
3 | import static io.qdrant.client.PointIdFactory.id;
4 |
5 | import com.google.common.primitives.Floats;
6 | import io.qdrant.client.grpc.Points.DenseVector;
7 | import io.qdrant.client.grpc.Points.MultiDenseVector;
8 | import io.qdrant.client.grpc.Points.PointId;
9 | import io.qdrant.client.grpc.Points.SparseVector;
10 | import io.qdrant.client.grpc.Points.VectorInput;
11 | import java.util.ArrayList;
12 | import java.util.List;
13 | import java.util.UUID;
14 | import java.util.stream.Collectors;
15 |
16 | /** Convenience methods for constructing {@link VectorInput} */
17 | public final class VectorInputFactory {
18 | private VectorInputFactory() {}
19 |
20 | /**
21 | * Creates a {@link VectorInput} from a list of floats
22 | *
23 | * @param values A map of vector names to values
24 | * @return A new instance of {@link VectorInput}
25 | */
26 | public static VectorInput vectorInput(List values) {
27 | return VectorInput.newBuilder().setDense(DenseVector.newBuilder().addAllData(values)).build();
28 | }
29 |
30 | /**
31 | * Creates a {@link VectorInput} from a list of floats
32 | *
33 | * @param values A list of values
34 | * @return A new instance of {@link VectorInput}
35 | */
36 | public static VectorInput vectorInput(float... values) {
37 | return VectorInput.newBuilder()
38 | .setDense(DenseVector.newBuilder().addAllData(Floats.asList(values)))
39 | .build();
40 | }
41 |
42 | /**
43 | * Creates a {@link VectorInput} from a list of floats and integers as indices
44 | *
45 | * @param vector The list of floats representing the vector.
46 | * @param indices The list of integers representing the indices.
47 | * @return A new instance of {@link VectorInput}
48 | */
49 | public static VectorInput vectorInput(List vector, List indices) {
50 | return VectorInput.newBuilder()
51 | .setSparse(SparseVector.newBuilder().addAllValues(vector).addAllIndices(indices).build())
52 | .build();
53 | }
54 |
55 | /**
56 | * Creates a {@link VectorInput} from a nested list of floats representing a multi vector
57 | *
58 | * @param vectors The nested list of floats.
59 | * @return A new instance of {@link VectorInput}
60 | */
61 | public static VectorInput multiVectorInput(List> vectors) {
62 | List denseVectors =
63 | vectors.stream()
64 | .map(v -> DenseVector.newBuilder().addAllData(v).build())
65 | .collect(Collectors.toList());
66 | return VectorInput.newBuilder()
67 | .setMultiDense(MultiDenseVector.newBuilder().addAllVectors(denseVectors).build())
68 | .build();
69 | }
70 |
71 | /**
72 | * Creates a {@link VectorInput} from a nested array of floats representing a multi vector
73 | *
74 | * @param vectors The nested array of floats.
75 | * @return A new instance of {@link VectorInput}
76 | */
77 | public static VectorInput multiVectorInput(float[][] vectors) {
78 | List denseVectors = new ArrayList<>();
79 | for (float[] vector : vectors) {
80 | denseVectors.add(DenseVector.newBuilder().addAllData(Floats.asList(vector)).build());
81 | }
82 | return VectorInput.newBuilder()
83 | .setMultiDense(MultiDenseVector.newBuilder().addAllVectors(denseVectors).build())
84 | .build();
85 | }
86 |
87 | /**
88 | * Creates a {@link VectorInput} from a {@link long}
89 | *
90 | * @param id The point id
91 | * @return a new instance of {@link VectorInput}
92 | */
93 | public static VectorInput vectorInput(long id) {
94 | return VectorInput.newBuilder().setId(id(id)).build();
95 | }
96 |
97 | /**
98 | * Creates a {@link VectorInput} from a {@link UUID}
99 | *
100 | * @param id The point id
101 | * @return a new instance of {@link VectorInput}
102 | */
103 | public static VectorInput vectorInput(UUID id) {
104 | return VectorInput.newBuilder().setId(id(id)).build();
105 | }
106 |
107 | /**
108 | * Creates a {@link VectorInput} from a {@link PointId}
109 | *
110 | * @param id The point id
111 | * @return a new instance of {@link VectorInput}
112 | */
113 | public static VectorInput vectorInput(PointId id) {
114 | return VectorInput.newBuilder().setId(id).build();
115 | }
116 |
117 | // /**
118 | // * Creates a {@link VectorInput} from a {@link Document}
119 | // *
120 | // * @param document An instance of {@link Document}
121 | // * @return a new instance of {@link VectorInput}
122 | // */
123 | // public static VectorInput vectorInput(Document document) {
124 | // return VectorInput.newBuilder().setDocument(document).build();
125 | // }
126 |
127 | // /**
128 | // * Creates a {@link VectorInput} from a an {@link Image}
129 | // *
130 | // * @param image An instance of {@link Image}
131 | // * @return a new instance of {@link VectorInput}
132 | // */
133 | // public static VectorInput vectorInput(Image image) {
134 | // return VectorInput.newBuilder().setImage(image).build();
135 | // }
136 |
137 | // /**
138 | // * Creates a {@link VectorInput} from a {@link InferenceObject}
139 | // *
140 | // * @param object An instance of {@link InferenceObject}
141 | // * @return a new instance of {@link VectorInput}
142 | // */
143 | // public static VectorInput vectorInput(InferenceObject object) {
144 | // return VectorInput.newBuilder().setObject(object).build();
145 | // }
146 | }
147 |
--------------------------------------------------------------------------------
/src/main/java/io/qdrant/client/VectorsFactory.java:
--------------------------------------------------------------------------------
1 | package io.qdrant.client;
2 |
3 | import static io.qdrant.client.VectorFactory.vector;
4 |
5 | import io.qdrant.client.grpc.Points.NamedVectors;
6 | import io.qdrant.client.grpc.Points.Vector;
7 | import io.qdrant.client.grpc.Points.Vectors;
8 | import java.util.List;
9 | import java.util.Map;
10 |
11 | /** Convenience methods for constructing {@link Vectors} */
12 | public final class VectorsFactory {
13 | private VectorsFactory() {}
14 |
15 | /**
16 | * Creates named vectors
17 | *
18 | * @param values A map of vector names to {@link Vector}
19 | * @return a new instance of {@link Vectors}
20 | */
21 | public static Vectors namedVectors(Map values) {
22 | return Vectors.newBuilder().setVectors(NamedVectors.newBuilder().putAllVectors(values)).build();
23 | }
24 |
25 | /**
26 | * Creates a vector
27 | *
28 | * @param values A list of values
29 | * @return a new instance of {@link Vectors}
30 | */
31 | public static Vectors vectors(List values) {
32 | return Vectors.newBuilder().setVector(vector(values)).build();
33 | }
34 |
35 | /**
36 | * Creates a vector
37 | *
38 | * @param values A list of values
39 | * @return a new instance of {@link Vectors}
40 | */
41 | public static Vectors vectors(float... values) {
42 | return Vectors.newBuilder().setVector(vector(values)).build();
43 | }
44 |
45 | /**
46 | * Creates a vector
47 | *
48 | * @param vector An instance of {@link Vector}
49 | * @return a new instance of {@link Vectors}
50 | */
51 | public static Vectors vectors(Vector vector) {
52 | return Vectors.newBuilder().setVector(vector).build();
53 | }
54 | }
55 |
--------------------------------------------------------------------------------
/src/main/java/io/qdrant/client/VersionsCompatibilityChecker.java:
--------------------------------------------------------------------------------
1 | package io.qdrant.client;
2 |
3 | import org.slf4j.Logger;
4 | import org.slf4j.LoggerFactory;
5 |
6 | class Version {
7 | private final int major;
8 | private final int minor;
9 |
10 | public Version(int major, int minor) {
11 | this.major = major;
12 | this.minor = minor;
13 | }
14 |
15 | public int getMajor() {
16 | return major;
17 | }
18 |
19 | public int getMinor() {
20 | return minor;
21 | }
22 | }
23 |
24 | /** Utility class to check compatibility between server's and client's versions. */
25 | public class VersionsCompatibilityChecker {
26 | private static final Logger logger = LoggerFactory.getLogger(VersionsCompatibilityChecker.class);
27 |
28 | /** Default constructor. */
29 | public VersionsCompatibilityChecker() {}
30 |
31 | private static Version parseVersion(String version) throws IllegalArgumentException {
32 | if (version.isEmpty()) {
33 | throw new IllegalArgumentException("Version is None");
34 | }
35 |
36 | try {
37 | String[] parts = version.split("\\.");
38 | int major = parts.length > 0 ? Integer.parseInt(parts[0]) : 0;
39 | int minor = parts.length > 1 ? Integer.parseInt(parts[1]) : 0;
40 |
41 | return new Version(major, minor);
42 | } catch (Exception e) {
43 | throw new IllegalArgumentException(
44 | "Unable to parse version, expected format: x.y[.z], found: " + version, e);
45 | }
46 | }
47 |
48 | /**
49 | * Compares server's and client's versions.
50 | *
51 | * @param clientVersion The client's version.
52 | * @param serverVersion The server's version.
53 | * @return True if the versions are compatible, false otherwise.
54 | */
55 | public static boolean isCompatible(String clientVersion, String serverVersion) {
56 | try {
57 | Version client = parseVersion(clientVersion);
58 | Version server = parseVersion(serverVersion);
59 |
60 | if (client.getMajor() != server.getMajor()) return false;
61 | return Math.abs(client.getMinor() - server.getMinor()) <= 1;
62 |
63 | } catch (IllegalArgumentException e) {
64 | logger.warn("Version comparison failed: {}", e.getMessage());
65 | return false;
66 | }
67 | }
68 | }
69 |
--------------------------------------------------------------------------------
/src/main/java/io/qdrant/client/WithPayloadSelectorFactory.java:
--------------------------------------------------------------------------------
1 | package io.qdrant.client;
2 |
3 | import io.qdrant.client.grpc.Points.PayloadExcludeSelector;
4 | import io.qdrant.client.grpc.Points.PayloadIncludeSelector;
5 | import io.qdrant.client.grpc.Points.WithPayloadSelector;
6 | import java.util.List;
7 |
8 | /** Convenience methods for constructing {@link WithPayloadSelector} */
9 | public final class WithPayloadSelectorFactory {
10 | private WithPayloadSelectorFactory() {}
11 |
12 | /**
13 | * Whether to include all payload in response.
14 | *
15 | * @param enable if true
, to include all payload, if false
, none.
16 | * @return a new instance of {@link WithPayloadSelector}
17 | */
18 | public static WithPayloadSelector enable(boolean enable) {
19 | return WithPayloadSelector.newBuilder().setEnable(enable).build();
20 | }
21 |
22 | /**
23 | * Which payload fields to include in response.
24 | *
25 | * @param fields the list of fields to include.
26 | * @return a new instance of {@link WithPayloadSelector}
27 | */
28 | public static WithPayloadSelector include(List fields) {
29 | return WithPayloadSelector.newBuilder()
30 | .setInclude(PayloadIncludeSelector.newBuilder().addAllFields(fields).build())
31 | .build();
32 | }
33 |
34 | /**
35 | * Which payload fields to exclude in response.
36 | *
37 | * @param fields the list of fields to exclude.
38 | * @return a new instance of {@link WithPayloadSelector}
39 | */
40 | public static WithPayloadSelector exclude(List fields) {
41 | return WithPayloadSelector.newBuilder()
42 | .setExclude(PayloadExcludeSelector.newBuilder().addAllFields(fields).build())
43 | .build();
44 | }
45 | }
46 |
--------------------------------------------------------------------------------
/src/main/java/io/qdrant/client/WithVectorsSelectorFactory.java:
--------------------------------------------------------------------------------
1 | package io.qdrant.client;
2 |
3 | import io.qdrant.client.grpc.Points;
4 | import io.qdrant.client.grpc.Points.WithVectorsSelector;
5 | import java.util.List;
6 |
7 | /** Convenience methods for constructing {@link WithVectorsSelector} */
8 | public final class WithVectorsSelectorFactory {
9 | private WithVectorsSelectorFactory() {}
10 |
11 | /**
12 | * Whether to include vectors in response.
13 | *
14 | * @param enable if true
, to include vectors, if false
, none.
15 | * @return a new instance of {@link WithVectorsSelector}
16 | */
17 | public static WithVectorsSelector enable(boolean enable) {
18 | return WithVectorsSelector.newBuilder().setEnable(enable).build();
19 | }
20 |
21 | /**
22 | * List of named vectors to include in response.
23 | *
24 | * @param names The names of vectors.
25 | * @return a new instance of {@link WithVectorsSelector}
26 | */
27 | public static WithVectorsSelector include(List names) {
28 | return WithVectorsSelector.newBuilder()
29 | .setInclude(Points.VectorsSelector.newBuilder().addAllNames(names))
30 | .build();
31 | }
32 | }
33 |
--------------------------------------------------------------------------------
/src/main/java/io/qdrant/client/package-info.java:
--------------------------------------------------------------------------------
1 | /** package */
2 | @ParametersAreNonnullByDefault
3 | package io.qdrant.client;
4 |
5 | import javax.annotation.ParametersAreNonnullByDefault;
6 |
--------------------------------------------------------------------------------
/src/test/java/io/qdrant/client/ApiKeyTest.java:
--------------------------------------------------------------------------------
1 | package io.qdrant.client;
2 |
3 | import static org.junit.jupiter.api.Assertions.assertEquals;
4 | import static org.junit.jupiter.api.Assertions.assertNotNull;
5 | import static org.junit.jupiter.api.Assertions.assertThrows;
6 |
7 | import io.grpc.Grpc;
8 | import io.grpc.InsecureChannelCredentials;
9 | import io.grpc.ManagedChannel;
10 | import io.grpc.Status;
11 | import io.grpc.StatusRuntimeException;
12 | import io.qdrant.client.grpc.QdrantOuterClass.HealthCheckReply;
13 | import io.qdrant.client.grpc.QdrantOuterClass.HealthCheckRequest;
14 | import java.util.concurrent.ExecutionException;
15 | import java.util.concurrent.TimeUnit;
16 | import org.junit.jupiter.api.AfterEach;
17 | import org.junit.jupiter.api.BeforeEach;
18 | import org.junit.jupiter.api.Test;
19 | import org.testcontainers.junit.jupiter.Container;
20 | import org.testcontainers.junit.jupiter.Testcontainers;
21 | import org.testcontainers.qdrant.QdrantContainer;
22 |
23 | @Testcontainers
24 | public class ApiKeyTest {
25 | @Container
26 | private static final QdrantContainer QDRANT_CONTAINER =
27 | new QdrantContainer(DockerImage.QDRANT_IMAGE)
28 | .withEnv("QDRANT__SERVICE__API_KEY", "password!");
29 |
30 | private ManagedChannel channel;
31 |
32 | @BeforeEach
33 | public void setup() {
34 | channel =
35 | Grpc.newChannelBuilder(
36 | QDRANT_CONTAINER.getGrpcHostAddress(), InsecureChannelCredentials.create())
37 | .build();
38 | }
39 |
40 | @AfterEach
41 | public void teardown() throws InterruptedException {
42 | channel.shutdownNow().awaitTermination(5, TimeUnit.SECONDS);
43 | }
44 |
45 | @Test
46 | public void client_with_api_key_can_connect() throws ExecutionException, InterruptedException {
47 | HealthCheckReply healthCheckReply;
48 | try (QdrantGrpcClient grpcClient =
49 | QdrantGrpcClient.newBuilder(channel).withApiKey("password!").build()) {
50 | healthCheckReply =
51 | grpcClient.qdrant().healthCheck(HealthCheckRequest.getDefaultInstance()).get();
52 | }
53 |
54 | assertNotNull(healthCheckReply.getTitle());
55 | assertNotNull(healthCheckReply.getVersion());
56 | }
57 |
58 | @Test
59 | public void client_without_api_key_cannot_connect() {
60 | try (QdrantGrpcClient grpcClient = QdrantGrpcClient.newBuilder(channel).build()) {
61 | ExecutionException executionException =
62 | assertThrows(
63 | ExecutionException.class,
64 | () -> grpcClient.qdrant().healthCheck(HealthCheckRequest.getDefaultInstance()).get());
65 | Throwable cause = executionException.getCause();
66 | assertEquals(StatusRuntimeException.class, cause.getClass());
67 | StatusRuntimeException statusRuntimeException = (StatusRuntimeException) cause;
68 | assertEquals(Status.Code.UNAUTHENTICATED, statusRuntimeException.getStatus().getCode());
69 | }
70 | }
71 | }
72 |
--------------------------------------------------------------------------------
/src/test/java/io/qdrant/client/CollectionsTest.java:
--------------------------------------------------------------------------------
1 | package io.qdrant.client;
2 |
3 | import static org.junit.jupiter.api.Assertions.assertEquals;
4 | import static org.junit.jupiter.api.Assertions.assertFalse;
5 | import static org.junit.jupiter.api.Assertions.assertThrows;
6 | import static org.junit.jupiter.api.Assertions.assertTrue;
7 |
8 | import io.grpc.Grpc;
9 | import io.grpc.InsecureChannelCredentials;
10 | import io.grpc.ManagedChannel;
11 | import io.grpc.Status;
12 | import io.grpc.StatusRuntimeException;
13 | import io.qdrant.client.grpc.Collections;
14 | import io.qdrant.client.grpc.Collections.AliasDescription;
15 | import io.qdrant.client.grpc.Collections.CollectionInfo;
16 | import io.qdrant.client.grpc.Collections.CollectionStatus;
17 | import io.qdrant.client.grpc.Collections.CreateCollection;
18 | import io.qdrant.client.grpc.Collections.Distance;
19 | import io.qdrant.client.grpc.Collections.VectorParams;
20 | import io.qdrant.client.grpc.Collections.VectorsConfig;
21 | import java.util.Comparator;
22 | import java.util.List;
23 | import java.util.concurrent.ExecutionException;
24 | import java.util.concurrent.TimeUnit;
25 | import java.util.stream.Collectors;
26 | import org.junit.jupiter.api.AfterEach;
27 | import org.junit.jupiter.api.BeforeEach;
28 | import org.junit.jupiter.api.Test;
29 | import org.junit.jupiter.api.TestInfo;
30 | import org.testcontainers.junit.jupiter.Container;
31 | import org.testcontainers.junit.jupiter.Testcontainers;
32 | import org.testcontainers.qdrant.QdrantContainer;
33 |
34 | @Testcontainers
35 | class CollectionsTest {
36 | @Container
37 | private static final QdrantContainer QDRANT_CONTAINER =
38 | new QdrantContainer(DockerImage.QDRANT_IMAGE);
39 |
40 | private QdrantClient client;
41 | private ManagedChannel channel;
42 | private String testName;
43 |
44 | private static CreateCollection getCreateCollection(String collectionName) {
45 | return CreateCollection.newBuilder()
46 | .setCollectionName(collectionName)
47 | .setVectorsConfig(
48 | VectorsConfig.newBuilder()
49 | .setParams(
50 | VectorParams.newBuilder().setDistance(Distance.Cosine).setSize(4).build())
51 | .build())
52 | .build();
53 | }
54 |
55 | @BeforeEach
56 | public void setup(TestInfo testInfo) {
57 | testName = testInfo.getDisplayName().replace("()", "");
58 | channel =
59 | Grpc.newChannelBuilder(
60 | QDRANT_CONTAINER.getGrpcHostAddress(), InsecureChannelCredentials.create())
61 | .build();
62 | QdrantGrpcClient grpcClient = QdrantGrpcClient.newBuilder(channel).build();
63 | client = new QdrantClient(grpcClient);
64 | }
65 |
66 | @AfterEach
67 | public void teardown() throws Exception {
68 | List collectionNames = client.listCollectionsAsync().get();
69 | for (String collectionName : collectionNames) {
70 | client.deleteCollectionAsync(collectionName).get();
71 | }
72 | client.close();
73 | channel.shutdownNow().awaitTermination(5, TimeUnit.SECONDS);
74 | }
75 |
76 | @Test
77 | void createCollection() throws ExecutionException, InterruptedException {
78 | client
79 | .createCollectionAsync(
80 | testName, VectorParams.newBuilder().setDistance(Distance.Cosine).setSize(4).build())
81 | .get();
82 |
83 | List collections = client.listCollectionsAsync().get();
84 | assertTrue(collections.contains(testName));
85 | }
86 |
87 | @Test
88 | void createCollection_for_existing_collection() throws ExecutionException, InterruptedException {
89 | CreateCollection createCollection = getCreateCollection(testName);
90 | client.createCollectionAsync(createCollection).get();
91 |
92 | ExecutionException exception =
93 | assertThrows(
94 | ExecutionException.class, () -> client.createCollectionAsync(createCollection).get());
95 | assertEquals(StatusRuntimeException.class, exception.getCause().getClass());
96 | }
97 |
98 | @Test
99 | void createCollection_with_no_collection_name() {
100 | CreateCollection createCollection =
101 | CreateCollection.newBuilder()
102 | .setVectorsConfig(
103 | VectorsConfig.newBuilder()
104 | .setParams(
105 | VectorParams.newBuilder().setDistance(Distance.Cosine).setSize(4).build())
106 | .build())
107 | .build();
108 |
109 | assertThrows(
110 | IllegalArgumentException.class, () -> client.createCollectionAsync(createCollection).get());
111 | }
112 |
113 | @Test
114 | public void recreateCollection() throws ExecutionException, InterruptedException {
115 | CreateCollection createCollection = getCreateCollection(testName);
116 | client.createCollectionAsync(createCollection).get();
117 | client.recreateCollectionAsync(createCollection).get();
118 |
119 | List collections = client.listCollectionsAsync().get();
120 | assertTrue(collections.contains(testName));
121 | }
122 |
123 | @Test
124 | public void updateCollection() throws ExecutionException, InterruptedException {
125 | CreateCollection createCollection = getCreateCollection(testName);
126 | client.createCollectionAsync(createCollection).get();
127 | client
128 | .updateCollectionAsync(
129 | Collections.UpdateCollection.newBuilder()
130 | .setCollectionName(testName)
131 | .setVectorsConfig(
132 | Collections.VectorsConfigDiff.newBuilder()
133 | .setParams(
134 | Collections.VectorParamsDiff.newBuilder().setOnDisk(false).build())
135 | .build())
136 | .build())
137 | .get();
138 | }
139 |
140 | @Test
141 | public void updateCollection_with_no_changes() throws ExecutionException, InterruptedException {
142 | CreateCollection createCollection = getCreateCollection(testName);
143 | client.createCollectionAsync(createCollection).get();
144 | client
145 | .updateCollectionAsync(
146 | Collections.UpdateCollection.newBuilder().setCollectionName(testName).build())
147 | .get();
148 | }
149 |
150 | @Test
151 | public void deleteCollection() throws ExecutionException, InterruptedException {
152 | CreateCollection createCollection = getCreateCollection(testName);
153 | client.createCollectionAsync(createCollection).get();
154 | client.deleteCollectionAsync(testName).get();
155 |
156 | List collections = client.listCollectionsAsync().get();
157 | assertTrue(collections.isEmpty());
158 | }
159 |
160 | @Test
161 | public void deleteCollection_with_missing_collection() {
162 | ExecutionException exception =
163 | assertThrows(ExecutionException.class, () -> client.deleteCollectionAsync(testName).get());
164 | Throwable cause = exception.getCause();
165 | assertEquals(QdrantException.class, cause.getClass());
166 | }
167 |
168 | @Test
169 | public void getCollectionInfo() throws ExecutionException, InterruptedException {
170 | CreateCollection createCollection = getCreateCollection(testName);
171 | client.createCollectionAsync(createCollection).get();
172 | CollectionInfo collectionInfo = client.getCollectionInfoAsync(testName).get();
173 | assertEquals(CollectionStatus.Green, collectionInfo.getStatus());
174 | }
175 |
176 | @Test
177 | public void getCollectionInfo_with_missing_collection() {
178 | ExecutionException exception =
179 | assertThrows(ExecutionException.class, () -> client.getCollectionInfoAsync(testName).get());
180 | Throwable cause = exception.getCause();
181 | assertEquals(StatusRuntimeException.class, cause.getClass());
182 | StatusRuntimeException underlyingException = (StatusRuntimeException) cause;
183 | assertEquals(Status.Code.NOT_FOUND, underlyingException.getStatus().getCode());
184 | }
185 |
186 | @Test
187 | public void collectionExists() throws ExecutionException, InterruptedException {
188 | assertFalse(client.collectionExistsAsync(testName).get());
189 |
190 | CreateCollection createCollection = getCreateCollection(testName);
191 | client.createCollectionAsync(createCollection).get();
192 | assertTrue(client.collectionExistsAsync(testName).get());
193 |
194 | client.deleteCollectionAsync(testName).get();
195 | assertFalse(client.collectionExistsAsync(testName).get());
196 | }
197 |
198 | @Test
199 | public void createAlias() throws ExecutionException, InterruptedException {
200 | CreateCollection createCollection = getCreateCollection(testName);
201 | client.createCollectionAsync(createCollection).get();
202 | client.createAliasAsync("alias_1", testName).get();
203 | }
204 |
205 | @Test
206 | public void listAliases() throws ExecutionException, InterruptedException {
207 | CreateCollection createCollection = getCreateCollection(testName);
208 | client.createCollectionAsync(createCollection).get();
209 | client.createAliasAsync("alias_1", testName).get();
210 | client.createAliasAsync("alias_2", testName).get();
211 |
212 | List aliasDescriptions = client.listAliasesAsync().get();
213 |
214 | List sorted =
215 | aliasDescriptions.stream()
216 | .sorted(Comparator.comparing(AliasDescription::getAliasName))
217 | .collect(Collectors.toList());
218 |
219 | assertEquals(2, sorted.size());
220 | assertEquals("alias_1", sorted.get(0).getAliasName());
221 | assertEquals(testName, sorted.get(0).getCollectionName());
222 | assertEquals("alias_2", sorted.get(1).getAliasName());
223 | assertEquals(testName, sorted.get(1).getCollectionName());
224 | }
225 |
226 | @Test
227 | public void listCollectionAliases() throws ExecutionException, InterruptedException {
228 | CreateCollection createCollection = getCreateCollection(testName);
229 | client.createCollectionAsync(createCollection).get();
230 | client.createAliasAsync("alias_1", testName).get();
231 | client.createAliasAsync("alias_2", testName).get();
232 |
233 | List aliases = client.listCollectionAliasesAsync(testName).get();
234 |
235 | List sorted = aliases.stream().sorted().collect(Collectors.toList());
236 |
237 | assertEquals(2, sorted.size());
238 | assertEquals("alias_1", sorted.get(0));
239 | assertEquals("alias_2", sorted.get(1));
240 | }
241 |
242 | @Test
243 | public void renameAlias() throws ExecutionException, InterruptedException {
244 | CreateCollection createCollection = getCreateCollection(testName);
245 | client.createCollectionAsync(createCollection).get();
246 | client.createAliasAsync("alias_1", testName).get();
247 | client.renameAliasAsync("alias_1", "alias_2").get();
248 |
249 | List aliases = client.listCollectionAliasesAsync(testName).get();
250 |
251 | List sorted = aliases.stream().sorted().collect(Collectors.toList());
252 |
253 | assertEquals(1, sorted.size());
254 | assertEquals("alias_2", sorted.get(0));
255 | }
256 |
257 | @Test
258 | public void deleteAlias() throws ExecutionException, InterruptedException {
259 | CreateCollection createCollection = getCreateCollection(testName);
260 | client.createCollectionAsync(createCollection).get();
261 | client.createAliasAsync("alias_1", testName).get();
262 | client.deleteAliasAsync("alias_1").get();
263 |
264 | List aliases = client.listCollectionAliasesAsync(testName).get();
265 | assertTrue(aliases.isEmpty());
266 | }
267 | }
268 |
--------------------------------------------------------------------------------
/src/test/java/io/qdrant/client/DockerImage.java:
--------------------------------------------------------------------------------
1 | package io.qdrant.client;
2 |
3 | public class DockerImage {
4 |
5 | public static final String QDRANT_IMAGE = "qdrant/qdrant:" + System.getProperty("qdrantVersion");
6 | }
7 |
--------------------------------------------------------------------------------
/src/test/java/io/qdrant/client/HealthTest.java:
--------------------------------------------------------------------------------
1 | package io.qdrant.client;
2 |
3 | import static org.junit.jupiter.api.Assertions.assertNotNull;
4 |
5 | import io.grpc.Grpc;
6 | import io.grpc.InsecureChannelCredentials;
7 | import io.grpc.ManagedChannel;
8 | import io.qdrant.client.grpc.QdrantOuterClass.HealthCheckReply;
9 | import java.util.concurrent.ExecutionException;
10 | import java.util.concurrent.TimeUnit;
11 | import org.junit.jupiter.api.AfterEach;
12 | import org.junit.jupiter.api.BeforeEach;
13 | import org.junit.jupiter.api.Test;
14 | import org.testcontainers.junit.jupiter.Container;
15 | import org.testcontainers.junit.jupiter.Testcontainers;
16 | import org.testcontainers.qdrant.QdrantContainer;
17 |
18 | @Testcontainers
19 | class HealthTest {
20 | @Container
21 | private static final QdrantContainer QDRANT_CONTAINER =
22 | new QdrantContainer(DockerImage.QDRANT_IMAGE);
23 |
24 | private QdrantClient client;
25 | private ManagedChannel channel;
26 |
27 | @BeforeEach
28 | public void setup() {
29 | channel =
30 | Grpc.newChannelBuilder(
31 | QDRANT_CONTAINER.getGrpcHostAddress(), InsecureChannelCredentials.create())
32 | .build();
33 | QdrantGrpcClient grpcClient = QdrantGrpcClient.newBuilder(channel).build();
34 | client = new QdrantClient(grpcClient);
35 | }
36 |
37 | @AfterEach
38 | public void teardown() throws Exception {
39 | client.close();
40 | channel.shutdownNow().awaitTermination(5, TimeUnit.SECONDS);
41 | }
42 |
43 | @Test
44 | public void healthCheck() throws ExecutionException, InterruptedException {
45 | HealthCheckReply healthCheckReply = client.healthCheckAsync().get();
46 | assertNotNull(healthCheckReply.getTitle());
47 | assertNotNull(healthCheckReply.getVersion());
48 | }
49 | }
50 |
--------------------------------------------------------------------------------
/src/test/java/io/qdrant/client/PointsTest.java:
--------------------------------------------------------------------------------
1 | package io.qdrant.client;
2 |
3 | import static io.qdrant.client.ConditionFactory.hasId;
4 | import static io.qdrant.client.ConditionFactory.matchKeyword;
5 | import static io.qdrant.client.PointIdFactory.id;
6 | import static io.qdrant.client.QueryFactory.fusion;
7 | import static io.qdrant.client.QueryFactory.nearest;
8 | import static io.qdrant.client.QueryFactory.orderBy;
9 | import static io.qdrant.client.QueryFactory.sample;
10 | import static io.qdrant.client.TargetVectorFactory.targetVector;
11 | import static io.qdrant.client.ValueFactory.value;
12 | import static io.qdrant.client.VectorFactory.vector;
13 | import static io.qdrant.client.VectorsFactory.vectors;
14 | import static org.junit.jupiter.api.Assertions.assertEquals;
15 | import static org.junit.jupiter.api.Assertions.assertFalse;
16 | import static org.junit.jupiter.api.Assertions.assertTrue;
17 |
18 | import io.grpc.Grpc;
19 | import io.grpc.InsecureChannelCredentials;
20 | import io.grpc.ManagedChannel;
21 | import io.qdrant.client.grpc.Collections;
22 | import io.qdrant.client.grpc.Collections.CollectionInfo;
23 | import io.qdrant.client.grpc.Collections.CreateCollection;
24 | import io.qdrant.client.grpc.Collections.Distance;
25 | import io.qdrant.client.grpc.Collections.PayloadSchemaType;
26 | import io.qdrant.client.grpc.Collections.VectorParams;
27 | import io.qdrant.client.grpc.Collections.VectorsConfig;
28 | import io.qdrant.client.grpc.Points;
29 | import io.qdrant.client.grpc.Points.BatchResult;
30 | import io.qdrant.client.grpc.Points.DiscoverPoints;
31 | import io.qdrant.client.grpc.Points.Filter;
32 | import io.qdrant.client.grpc.Points.Fusion;
33 | import io.qdrant.client.grpc.Points.PointGroup;
34 | import io.qdrant.client.grpc.Points.PointStruct;
35 | import io.qdrant.client.grpc.Points.PointVectors;
36 | import io.qdrant.client.grpc.Points.PointsIdsList;
37 | import io.qdrant.client.grpc.Points.PointsSelector;
38 | import io.qdrant.client.grpc.Points.PointsUpdateOperation;
39 | import io.qdrant.client.grpc.Points.PointsUpdateOperation.ClearPayload;
40 | import io.qdrant.client.grpc.Points.PointsUpdateOperation.UpdateVectors;
41 | import io.qdrant.client.grpc.Points.PrefetchQuery;
42 | import io.qdrant.client.grpc.Points.QueryPointGroups;
43 | import io.qdrant.client.grpc.Points.QueryPoints;
44 | import io.qdrant.client.grpc.Points.RecommendPointGroups;
45 | import io.qdrant.client.grpc.Points.RecommendPoints;
46 | import io.qdrant.client.grpc.Points.RetrievedPoint;
47 | import io.qdrant.client.grpc.Points.Sample;
48 | import io.qdrant.client.grpc.Points.ScoredPoint;
49 | import io.qdrant.client.grpc.Points.ScrollPoints;
50 | import io.qdrant.client.grpc.Points.ScrollResponse;
51 | import io.qdrant.client.grpc.Points.SearchPointGroups;
52 | import io.qdrant.client.grpc.Points.SearchPoints;
53 | import io.qdrant.client.grpc.Points.UpdateResult;
54 | import io.qdrant.client.grpc.Points.UpdateStatus;
55 | import io.qdrant.client.grpc.Points.VectorsOutput;
56 | import java.util.Arrays;
57 | import java.util.List;
58 | import java.util.concurrent.ExecutionException;
59 | import java.util.concurrent.TimeUnit;
60 | import org.junit.jupiter.api.AfterEach;
61 | import org.junit.jupiter.api.BeforeEach;
62 | import org.junit.jupiter.api.Test;
63 | import org.junit.jupiter.api.TestInfo;
64 | import org.testcontainers.junit.jupiter.Container;
65 | import org.testcontainers.junit.jupiter.Testcontainers;
66 | import org.testcontainers.qdrant.QdrantContainer;
67 | import org.testcontainers.shaded.com.google.common.collect.ImmutableList;
68 | import org.testcontainers.shaded.com.google.common.collect.ImmutableMap;
69 | import org.testcontainers.shaded.com.google.common.collect.ImmutableSet;
70 |
71 | @Testcontainers
72 | class PointsTest {
73 | @Container
74 | private static final QdrantContainer QDRANT_CONTAINER =
75 | new QdrantContainer(DockerImage.QDRANT_IMAGE);
76 |
77 | private QdrantClient client;
78 | private ManagedChannel channel;
79 | private String testName;
80 |
81 | @BeforeEach
82 | public void setup(TestInfo testInfo) {
83 | testName = testInfo.getDisplayName().replace("()", "");
84 | channel =
85 | Grpc.newChannelBuilder(
86 | QDRANT_CONTAINER.getGrpcHostAddress(), InsecureChannelCredentials.create())
87 | .build();
88 | QdrantGrpcClient grpcClient = QdrantGrpcClient.newBuilder(channel).build();
89 | client = new QdrantClient(grpcClient);
90 | }
91 |
92 | @AfterEach
93 | public void teardown() throws Exception {
94 | List collectionNames = client.listCollectionsAsync().get();
95 | for (String collectionName : collectionNames) {
96 | client.deleteCollectionAsync(collectionName).get();
97 | }
98 | client.close();
99 | channel.shutdownNow().awaitTermination(5, TimeUnit.SECONDS);
100 | }
101 |
102 | @Test
103 | public void retrieve() throws ExecutionException, InterruptedException {
104 | createAndSeedCollection(testName);
105 |
106 | List points = client.retrieveAsync(testName, id(9), null).get();
107 |
108 | assertEquals(1, points.size());
109 | RetrievedPoint point = points.get(0);
110 | assertEquals(id(9), point.getId());
111 | assertEquals(ImmutableSet.of("foo", "bar", "date"), point.getPayloadMap().keySet());
112 | assertEquals(value("goodbye"), point.getPayloadMap().get("foo"));
113 | assertEquals(value(2), point.getPayloadMap().get("bar"));
114 | assertEquals(VectorsOutput.getDefaultInstance(), point.getVectors());
115 | }
116 |
117 | @Test
118 | public void retrieve_with_vector_without_payload()
119 | throws ExecutionException, InterruptedException {
120 | createAndSeedCollection(testName);
121 |
122 | List points = client.retrieveAsync(testName, id(8), false, true, null).get();
123 |
124 | assertEquals(1, points.size());
125 | RetrievedPoint point = points.get(0);
126 | assertEquals(id(8), point.getId());
127 | assertTrue(point.getPayloadMap().isEmpty());
128 | assertEquals(
129 | VectorsOutput.VectorsOptionsCase.VECTOR, point.getVectors().getVectorsOptionsCase());
130 | }
131 |
132 | @Test
133 | public void setPayload() throws ExecutionException, InterruptedException {
134 | createAndSeedCollection(testName);
135 |
136 | client
137 | .setPayloadAsync(
138 | testName, ImmutableMap.of("bar", value("some bar")), id(9), null, null, null)
139 | .get();
140 |
141 | List points = client.retrieveAsync(testName, id(9), null).get();
142 |
143 | assertEquals(1, points.size());
144 | RetrievedPoint point = points.get(0);
145 | assertEquals(id(9), point.getId());
146 | assertEquals(ImmutableSet.of("foo", "bar", "date"), point.getPayloadMap().keySet());
147 | assertEquals(value("some bar"), point.getPayloadMap().get("bar"));
148 | assertEquals(value("goodbye"), point.getPayloadMap().get("foo"));
149 | }
150 |
151 | @Test
152 | public void overwritePayload() throws ExecutionException, InterruptedException {
153 | createAndSeedCollection(testName);
154 |
155 | client
156 | .overwritePayloadAsync(
157 | testName, ImmutableMap.of("bar", value("some bar")), id(9), null, null, null)
158 | .get();
159 |
160 | List points = client.retrieveAsync(testName, id(9), null).get();
161 |
162 | assertEquals(1, points.size());
163 | RetrievedPoint point = points.get(0);
164 | assertEquals(id(9), point.getId());
165 | assertEquals(ImmutableSet.of("bar"), point.getPayloadMap().keySet());
166 | assertEquals(value("some bar"), point.getPayloadMap().get("bar"));
167 | }
168 |
169 | @Test
170 | public void deletePayload() throws ExecutionException, InterruptedException {
171 | createAndSeedCollection(testName);
172 |
173 | client
174 | .setPayloadAsync(
175 | testName, ImmutableMap.of("bar", value("some bar")), id(9), null, null, null)
176 | .get();
177 |
178 | client.deletePayloadAsync(testName, ImmutableList.of("foo"), id(9), null, null, null).get();
179 |
180 | List points = client.retrieveAsync(testName, id(9), null).get();
181 |
182 | assertEquals(1, points.size());
183 | RetrievedPoint point = points.get(0);
184 | assertEquals(id(9), point.getId());
185 | assertEquals(ImmutableSet.of("bar", "date"), point.getPayloadMap().keySet());
186 | assertEquals(value("some bar"), point.getPayloadMap().get("bar"));
187 | }
188 |
189 | @Test
190 | public void clearPayload() throws ExecutionException, InterruptedException {
191 | createAndSeedCollection(testName);
192 |
193 | client.clearPayloadAsync(testName, id(9), true, null, null).get();
194 |
195 | List points = client.retrieveAsync(testName, id(9), null).get();
196 |
197 | assertEquals(1, points.size());
198 | RetrievedPoint point = points.get(0);
199 | assertEquals(id(9), point.getId());
200 | assertTrue(point.getPayloadMap().isEmpty());
201 | }
202 |
203 | @Test
204 | public void createFieldIndex() throws ExecutionException, InterruptedException {
205 | createAndSeedCollection(testName);
206 |
207 | UpdateResult result =
208 | client
209 | .createPayloadIndexAsync(
210 | testName, "foo", PayloadSchemaType.Keyword, null, null, null, null)
211 | .get();
212 |
213 | assertEquals(UpdateStatus.Completed, result.getStatus());
214 |
215 | result =
216 | client
217 | .createPayloadIndexAsync(
218 | testName, "bar", PayloadSchemaType.Integer, null, null, null, null)
219 | .get();
220 |
221 | assertEquals(UpdateStatus.Completed, result.getStatus());
222 |
223 | CollectionInfo collectionInfo = client.getCollectionInfoAsync(testName).get();
224 | assertEquals(ImmutableSet.of("foo", "bar"), collectionInfo.getPayloadSchemaMap().keySet());
225 | assertEquals(
226 | PayloadSchemaType.Keyword, collectionInfo.getPayloadSchemaMap().get("foo").getDataType());
227 | assertEquals(
228 | PayloadSchemaType.Integer, collectionInfo.getPayloadSchemaMap().get("bar").getDataType());
229 | }
230 |
231 | @Test
232 | public void createDatetimeFieldIndex() throws ExecutionException, InterruptedException {
233 | createAndSeedCollection(testName);
234 |
235 | UpdateResult result =
236 | client
237 | .createPayloadIndexAsync(
238 | testName, "date", PayloadSchemaType.Datetime, null, null, null, null)
239 | .get();
240 |
241 | assertEquals(UpdateStatus.Completed, result.getStatus());
242 |
243 | CollectionInfo collectionInfo = client.getCollectionInfoAsync(testName).get();
244 | assertEquals(ImmutableSet.of("date"), collectionInfo.getPayloadSchemaMap().keySet());
245 | assertEquals(
246 | PayloadSchemaType.Datetime, collectionInfo.getPayloadSchemaMap().get("date").getDataType());
247 | }
248 |
249 | @Test
250 | public void deleteFieldIndex() throws ExecutionException, InterruptedException {
251 | createAndSeedCollection(testName);
252 |
253 | UpdateResult result =
254 | client
255 | .createPayloadIndexAsync(
256 | testName, "foo", PayloadSchemaType.Keyword, null, null, null, null)
257 | .get();
258 | assertEquals(UpdateStatus.Completed, result.getStatus());
259 |
260 | result = client.deletePayloadIndexAsync(testName, "foo", null, null, null).get();
261 | assertEquals(UpdateStatus.Completed, result.getStatus());
262 | }
263 |
264 | @Test
265 | public void search() throws ExecutionException, InterruptedException {
266 | createAndSeedCollection(testName);
267 |
268 | List points =
269 | client
270 | .searchAsync(
271 | SearchPoints.newBuilder()
272 | .setCollectionName(testName)
273 | .setWithPayload(WithPayloadSelectorFactory.enable(true))
274 | .addAllVector(ImmutableList.of(10.4f, 11.4f))
275 | .setLimit(1)
276 | .build())
277 | .get();
278 |
279 | assertEquals(1, points.size());
280 | ScoredPoint point = points.get(0);
281 | assertEquals(id(9), point.getId());
282 | assertEquals(ImmutableSet.of("foo", "bar", "date"), point.getPayloadMap().keySet());
283 | assertEquals(value("goodbye"), point.getPayloadMap().get("foo"));
284 | assertEquals(value(2), point.getPayloadMap().get("bar"));
285 | assertFalse(point.getVectors().hasVector());
286 | }
287 |
288 | @Test
289 | public void searchBatch() throws ExecutionException, InterruptedException {
290 | createAndSeedCollection(testName);
291 |
292 | List batchResults =
293 | client
294 | .searchBatchAsync(
295 | testName,
296 | ImmutableList.of(
297 | SearchPoints.newBuilder()
298 | .addAllVector(ImmutableList.of(10.4f, 11.4f))
299 | .setLimit(1)
300 | .build(),
301 | SearchPoints.newBuilder()
302 | .addAllVector(ImmutableList.of(3.4f, 4.4f))
303 | .setLimit(1)
304 | .build()),
305 | null)
306 | .get();
307 |
308 | assertEquals(2, batchResults.size());
309 | BatchResult result = batchResults.get(0);
310 | assertEquals(1, result.getResultCount());
311 | assertEquals(id(9), result.getResult(0).getId());
312 | result = batchResults.get(1);
313 | assertEquals(1, result.getResultCount());
314 | assertEquals(id(8), result.getResult(0).getId());
315 | }
316 |
317 | @Test
318 | public void searchGroups() throws ExecutionException, InterruptedException {
319 | createAndSeedCollection(testName);
320 |
321 | client
322 | .upsertAsync(
323 | testName,
324 | ImmutableList.of(
325 | PointStruct.newBuilder()
326 | .setId(id(10))
327 | .setVectors(VectorsFactory.vectors(30f, 31f))
328 | .putAllPayload(ImmutableMap.of("foo", value("hello")))
329 | .build()))
330 | .get();
331 |
332 | List groups =
333 | client
334 | .searchGroupsAsync(
335 | SearchPointGroups.newBuilder()
336 | .setCollectionName(testName)
337 | .addAllVector(ImmutableList.of(10.4f, 11.4f))
338 | .setGroupBy("foo")
339 | .setGroupSize(2)
340 | .setLimit(10)
341 | .build())
342 | .get();
343 |
344 | assertEquals(2, groups.size());
345 | assertEquals(1, groups.stream().filter(g -> g.getHitsCount() == 2).count());
346 | assertEquals(1, groups.stream().filter(g -> g.getHitsCount() == 1).count());
347 | }
348 |
349 | @Test
350 | public void scroll() throws ExecutionException, InterruptedException {
351 | createAndSeedCollection(testName);
352 |
353 | ScrollResponse scrollResponse =
354 | client
355 | .scrollAsync(ScrollPoints.newBuilder().setCollectionName(testName).setLimit(1).build())
356 | .get();
357 |
358 | assertEquals(1, scrollResponse.getResultCount());
359 | assertTrue(scrollResponse.hasNextPageOffset());
360 |
361 | scrollResponse =
362 | client
363 | .scrollAsync(
364 | ScrollPoints.newBuilder()
365 | .setCollectionName(testName)
366 | .setLimit(1)
367 | .setOffset(scrollResponse.getNextPageOffset())
368 | .build())
369 | .get();
370 |
371 | assertEquals(1, scrollResponse.getResultCount());
372 | assertFalse(scrollResponse.hasNextPageOffset());
373 | }
374 |
375 | @Test
376 | public void scrollWithOrdering() throws ExecutionException, InterruptedException {
377 | createAndSeedCollection(testName);
378 |
379 | Collections.PayloadIndexParams params =
380 | Collections.PayloadIndexParams.newBuilder()
381 | .setIntegerIndexParams(
382 | Collections.IntegerIndexParams.newBuilder().setLookup(false).setRange(true).build())
383 | .build();
384 |
385 | UpdateResult resultIndex =
386 | client
387 | .createPayloadIndexAsync(
388 | testName, "bar", PayloadSchemaType.Integer, params, true, null, null)
389 | .get();
390 |
391 | assertEquals(UpdateStatus.Completed, resultIndex.getStatus());
392 |
393 | CollectionInfo collectionInfo = client.getCollectionInfoAsync(testName).get();
394 | assertEquals(ImmutableSet.of("bar"), collectionInfo.getPayloadSchemaMap().keySet());
395 |
396 | ScrollResponse scrollResponse =
397 | client
398 | .scrollAsync(
399 | ScrollPoints.newBuilder()
400 | .setCollectionName(testName)
401 | .setLimit(1)
402 | .setOrderBy(
403 | Points.OrderBy.newBuilder()
404 | .setDirection(Points.Direction.Desc)
405 | .setKey("bar")
406 | .build())
407 | .build())
408 | .get();
409 |
410 | assertEquals(1, scrollResponse.getResultCount());
411 | assertFalse(scrollResponse.hasNextPageOffset());
412 | assertEquals(scrollResponse.getResult(0).getId(), id(9));
413 | }
414 |
415 | @Test
416 | public void recommend() throws ExecutionException, InterruptedException {
417 | createAndSeedCollection(testName);
418 |
419 | List points =
420 | client
421 | .recommendAsync(
422 | RecommendPoints.newBuilder()
423 | .setCollectionName(testName)
424 | .addPositive(id(8))
425 | .setLimit(1)
426 | .build())
427 | .get();
428 |
429 | assertEquals(1, points.size());
430 | assertEquals(id(9), points.get(0).getId());
431 | }
432 |
433 | @Test
434 | public void recommendBatch() throws ExecutionException, InterruptedException {
435 | createAndSeedCollection(testName);
436 |
437 | List batchResults =
438 | client
439 | .recommendBatchAsync(
440 | testName,
441 | ImmutableList.of(
442 | RecommendPoints.newBuilder()
443 | .setCollectionName(testName)
444 | .addPositive(id(8))
445 | .setLimit(1)
446 | .build(),
447 | RecommendPoints.newBuilder()
448 | .setCollectionName(testName)
449 | .addPositive(id(9))
450 | .setLimit(1)
451 | .build()),
452 | null)
453 | .get();
454 |
455 | assertEquals(2, batchResults.size());
456 | BatchResult result = batchResults.get(0);
457 | assertEquals(1, result.getResultCount());
458 | assertEquals(id(9), result.getResult(0).getId());
459 | result = batchResults.get(1);
460 | assertEquals(1, result.getResultCount());
461 | assertEquals(id(8), result.getResult(0).getId());
462 | }
463 |
464 | @Test
465 | public void recommendGroups() throws ExecutionException, InterruptedException {
466 | createAndSeedCollection(testName);
467 |
468 | client
469 | .upsertAsync(
470 | testName,
471 | ImmutableList.of(
472 | PointStruct.newBuilder()
473 | .setId(id(10))
474 | .setVectors(VectorsFactory.vectors(30f, 31f))
475 | .putAllPayload(ImmutableMap.of("foo", value("hello")))
476 | .build()))
477 | .get();
478 |
479 | List groups =
480 | client
481 | .recommendGroupsAsync(
482 | RecommendPointGroups.newBuilder()
483 | .setCollectionName(testName)
484 | .setGroupBy("foo")
485 | .addPositive(id(9))
486 | .setGroupSize(2)
487 | .setLimit(10)
488 | .build())
489 | .get();
490 |
491 | assertEquals(1, groups.size());
492 | assertEquals(2, groups.get(0).getHitsCount());
493 | }
494 |
495 | @Test
496 | public void discover() throws ExecutionException, InterruptedException {
497 | createAndSeedCollection(testName);
498 |
499 | List points =
500 | client
501 | .discoverAsync(
502 | DiscoverPoints.newBuilder()
503 | .setCollectionName(testName)
504 | .setTarget(targetVector(vector(ImmutableList.of(10.4f, 11.4f))))
505 | .setLimit(1)
506 | .build())
507 | .get();
508 |
509 | assertEquals(1, points.size());
510 | assertEquals(id(9), points.get(0).getId());
511 | }
512 |
513 | @Test
514 | public void discoverBatch() throws ExecutionException, InterruptedException {
515 | createAndSeedCollection(testName);
516 |
517 | List batchResults =
518 | client
519 | .discoverBatchAsync(
520 | testName,
521 | ImmutableList.of(
522 | DiscoverPoints.newBuilder()
523 | .setCollectionName(testName)
524 | .setTarget(targetVector(vector(ImmutableList.of(10.4f, 11.4f))))
525 | .setLimit(1)
526 | .build(),
527 | DiscoverPoints.newBuilder()
528 | .setCollectionName(testName)
529 | .setTarget(targetVector(vector(ImmutableList.of(3.5f, 4.5f))))
530 | .setLimit(1)
531 | .build()),
532 | null)
533 | .get();
534 |
535 | assertEquals(2, batchResults.size());
536 | BatchResult result = batchResults.get(0);
537 | assertEquals(1, result.getResultCount());
538 | assertEquals(id(9), result.getResult(0).getId());
539 | result = batchResults.get(1);
540 | assertEquals(1, result.getResultCount());
541 | assertEquals(id(8), result.getResult(0).getId());
542 | }
543 |
544 | @Test
545 | public void count() throws ExecutionException, InterruptedException {
546 | createAndSeedCollection(testName);
547 | Long count = client.countAsync(testName).get();
548 | assertEquals(2, count);
549 | }
550 |
551 | @Test
552 | public void count_with_filter() throws ExecutionException, InterruptedException {
553 | createAndSeedCollection(testName);
554 | Long count =
555 | client
556 | .countAsync(
557 | testName,
558 | Filter.newBuilder()
559 | .addMust(hasId(id(9)))
560 | .addMust(matchKeyword("foo", "goodbye"))
561 | .build(),
562 | null)
563 | .get();
564 | assertEquals(1, count);
565 | }
566 |
567 | @Test
568 | public void delete_by_id() throws ExecutionException, InterruptedException {
569 | createAndSeedCollection(testName);
570 |
571 | List points = client.retrieveAsync(testName, id(8), false, false, null).get();
572 |
573 | assertEquals(1, points.size());
574 |
575 | client.deleteAsync(testName, ImmutableList.of(id(8))).get();
576 |
577 | points = client.retrieveAsync(testName, id(8), false, false, null).get();
578 |
579 | assertEquals(0, points.size());
580 | }
581 |
582 | @Test
583 | public void delete_by_filter() throws ExecutionException, InterruptedException {
584 | createAndSeedCollection(testName);
585 |
586 | List points = client.retrieveAsync(testName, id(8), false, false, null).get();
587 |
588 | assertEquals(1, points.size());
589 |
590 | client
591 | .deleteAsync(testName, Filter.newBuilder().addMust(matchKeyword("foo", "hello")).build())
592 | .get();
593 |
594 | points = client.retrieveAsync(testName, id(8), false, false, null).get();
595 |
596 | assertEquals(0, points.size());
597 | }
598 |
599 | @Test
600 | public void batchPointUpdate() throws ExecutionException, InterruptedException {
601 | createAndSeedCollection(testName);
602 |
603 | List operations =
604 | Arrays.asList(
605 | PointsUpdateOperation.newBuilder()
606 | .setClearPayload(
607 | ClearPayload.newBuilder()
608 | .setPoints(
609 | PointsSelector.newBuilder()
610 | .setPoints(PointsIdsList.newBuilder().addIds(id(9))))
611 | .build())
612 | .build(),
613 | PointsUpdateOperation.newBuilder()
614 | .setUpdateVectors(
615 | UpdateVectors.newBuilder()
616 | .addPoints(
617 | PointVectors.newBuilder().setId(id(9)).setVectors(vectors(0.6f, 0.7f))))
618 | .build());
619 |
620 | List response = client.batchUpdateAsync(testName, operations).get();
621 |
622 | response.forEach(result -> assertEquals(UpdateStatus.Completed, result.getStatus()));
623 | }
624 |
625 | @Test
626 | public void query() throws ExecutionException, InterruptedException {
627 | createAndSeedCollection(testName);
628 |
629 | List points =
630 | client.queryAsync(QueryPoints.newBuilder().setCollectionName(testName).build()).get();
631 |
632 | assertEquals(2, points.size());
633 | assertEquals(points.get(0).getId(), id(8));
634 | assertEquals(points.get(1).getId(), id(9));
635 |
636 | points =
637 | client
638 | .queryAsync(QueryPoints.newBuilder().setCollectionName(testName).setLimit(1).build())
639 | .get();
640 |
641 | assertEquals(1, points.size());
642 | assertEquals(id(8), points.get(0).getId());
643 | }
644 |
645 | @Test
646 | public void queryWithFilter() throws ExecutionException, InterruptedException {
647 | createAndSeedCollection(testName);
648 |
649 | List points =
650 | client
651 | .queryAsync(
652 | QueryPoints.newBuilder()
653 | .setCollectionName(testName)
654 | .setFilter(Filter.newBuilder().addMust(matchKeyword("foo", "hello")).build())
655 | .build())
656 | .get();
657 |
658 | assertEquals(1, points.size());
659 | assertEquals(id(8), points.get(0).getId());
660 | }
661 |
662 | @Test
663 | public void queryNearestWithID() throws ExecutionException, InterruptedException {
664 | createAndSeedCollection(testName);
665 |
666 | List points =
667 | client
668 | .queryAsync(
669 | QueryPoints.newBuilder().setCollectionName(testName).setQuery(nearest(8)).build())
670 | .get();
671 |
672 | assertEquals(1, points.size());
673 | assertEquals(id(9), points.get(0).getId());
674 | }
675 |
676 | @Test
677 | public void queryNearestWithVector() throws ExecutionException, InterruptedException {
678 | createAndSeedCollection(testName);
679 |
680 | List points =
681 | client
682 | .queryAsync(
683 | QueryPoints.newBuilder()
684 | .setCollectionName(testName)
685 | .setQuery(nearest(10.5f, 11.5f))
686 | .build())
687 | .get();
688 |
689 | assertEquals(2, points.size());
690 | assertEquals(id(9), points.get(0).getId());
691 | }
692 |
693 | @Test
694 | public void queryOrderBy() throws ExecutionException, InterruptedException {
695 | createAndSeedCollection(testName);
696 |
697 | Collections.PayloadIndexParams params =
698 | Collections.PayloadIndexParams.newBuilder()
699 | .setIntegerIndexParams(
700 | Collections.IntegerIndexParams.newBuilder().setLookup(false).setRange(true).build())
701 | .build();
702 |
703 | UpdateResult resultIndex =
704 | client
705 | .createPayloadIndexAsync(
706 | testName, "bar", PayloadSchemaType.Integer, params, true, null, null)
707 | .get();
708 |
709 | assertEquals(UpdateStatus.Completed, resultIndex.getStatus());
710 |
711 | CollectionInfo collectionInfo = client.getCollectionInfoAsync(testName).get();
712 | assertEquals(ImmutableSet.of("bar"), collectionInfo.getPayloadSchemaMap().keySet());
713 | assertEquals(
714 | PayloadSchemaType.Integer, collectionInfo.getPayloadSchemaMap().get("bar").getDataType());
715 |
716 | List points =
717 | client
718 | .queryAsync(
719 | QueryPoints.newBuilder()
720 | .setCollectionName(testName)
721 | .setLimit(1)
722 | .setQuery(orderBy("bar"))
723 | .build())
724 | .get();
725 |
726 | assertEquals(1, points.size());
727 | assertEquals(id(8), points.get(0).getId());
728 | }
729 |
730 | @Test
731 | public void queryWithPrefetchLimit() throws ExecutionException, InterruptedException {
732 | createAndSeedCollection(testName);
733 |
734 | List points =
735 | client
736 | .queryAsync(
737 | QueryPoints.newBuilder()
738 | .addPrefetch(PrefetchQuery.newBuilder().setLimit(1).build())
739 | .setCollectionName(testName)
740 | .setQuery(nearest(10.5f, 11.5f))
741 | .build())
742 | .get();
743 |
744 | assertEquals(1, points.size());
745 | }
746 |
747 | @Test
748 | public void queryWithPrefetchAndFusion() throws ExecutionException, InterruptedException {
749 | createAndSeedCollection(testName);
750 |
751 | List points =
752 | client
753 | .queryAsync(
754 | QueryPoints.newBuilder()
755 | .addPrefetch(PrefetchQuery.newBuilder().setQuery(nearest(10.5f, 11.5f)).build())
756 | .addPrefetch(PrefetchQuery.newBuilder().setQuery(nearest(3.5f, 4.5f)).build())
757 | .setCollectionName(testName)
758 | .setQuery(fusion(Fusion.RRF))
759 | .build())
760 | .get();
761 |
762 | assertEquals(2, points.size());
763 | }
764 |
765 | @Test
766 | public void queryWithSampling() throws ExecutionException, InterruptedException {
767 | createAndSeedCollection(testName);
768 |
769 | List points =
770 | client
771 | .queryAsync(
772 | QueryPoints.newBuilder()
773 | .setCollectionName(testName)
774 | .setQuery(sample(Sample.Random))
775 | .setLimit(1)
776 | .build())
777 | .get();
778 |
779 | assertEquals(1, points.size());
780 | }
781 |
782 | @Test
783 | public void queryGroups() throws ExecutionException, InterruptedException {
784 | createAndSeedCollection(testName);
785 |
786 | client
787 | .upsertAsync(
788 | testName,
789 | ImmutableList.of(
790 | PointStruct.newBuilder()
791 | .setId(id(10))
792 | .setVectors(VectorsFactory.vectors(30f, 31f))
793 | .putAllPayload(ImmutableMap.of("foo", value("hello")))
794 | .build()))
795 | .get();
796 | // 3 points in total, 2 with "foo" = "hello" and 1 with "foo" = "goodbye"
797 |
798 | List groups =
799 | client
800 | .queryGroupsAsync(
801 | QueryPointGroups.newBuilder()
802 | .setCollectionName(testName)
803 | .setQuery(nearest(ImmutableList.of(10.4f, 11.4f)))
804 | .setGroupBy("foo")
805 | .setGroupSize(2)
806 | .setLimit(10)
807 | .build())
808 | .get();
809 |
810 | assertEquals(2, groups.size());
811 | // A group with 2 hits because of 2 points with "foo" = "hello"
812 | assertEquals(1, groups.stream().filter(g -> g.getHitsCount() == 2).count());
813 | // A group with 1 hit because of 1 point with "foo" = "goodbye"
814 | assertEquals(1, groups.stream().filter(g -> g.getHitsCount() == 1).count());
815 | }
816 |
817 | @Test
818 | public void searchMatrixOffsets() throws ExecutionException, InterruptedException {
819 | createAndSeedCollection(testName);
820 |
821 | Points.SearchMatrixOffsets offsets =
822 | client
823 | .searchMatrixOffsetsAsync(
824 | Points.SearchMatrixPoints.newBuilder()
825 | .setCollectionName(testName)
826 | .setSample(3)
827 | .setLimit(2)
828 | .build())
829 | .get();
830 |
831 | // Number of ids matches the limit
832 | assertEquals(2, offsets.getIdsCount());
833 | }
834 |
835 | @Test
836 | public void searchMatrixPairs() throws ExecutionException, InterruptedException {
837 | createAndSeedCollection(testName);
838 |
839 | Points.SearchMatrixPairs pairs =
840 | client
841 | .searchMatrixPairsAsync(
842 | Points.SearchMatrixPoints.newBuilder()
843 | .setCollectionName(testName)
844 | .setSample(3)
845 | .setLimit(2)
846 | .build())
847 | .get();
848 |
849 | // Number of ids matches the limit
850 | assertEquals(2, pairs.getPairsCount());
851 | }
852 |
853 | @Test
854 | public void facets() throws ExecutionException, InterruptedException {
855 | createAndSeedCollection(testName);
856 |
857 | // create payload index for "foo" field
858 | UpdateResult result =
859 | client
860 | .createPayloadIndexAsync(
861 | testName, "foo", PayloadSchemaType.Keyword, null, null, null, null)
862 | .get();
863 |
864 | assertEquals(UpdateStatus.Completed, result.getStatus());
865 |
866 | List facets =
867 | client
868 | .facetAsync(
869 | Points.FacetCounts.newBuilder()
870 | .setCollectionName(testName)
871 | .setKey("foo")
872 | .setLimit(2)
873 | .build())
874 | .get();
875 |
876 | // Number of facets matches the limit
877 | assertEquals(2, facets.size());
878 | // validate hits
879 | assertEquals(
880 | 1,
881 | facets.stream()
882 | .filter(f -> f.getValue().getStringValue().equals("hello") && f.getCount() == 1)
883 | .count());
884 | assertEquals(
885 | 1,
886 | facets.stream()
887 | .filter(f -> f.getValue().getStringValue().equals("goodbye") && f.getCount() == 1)
888 | .count());
889 | }
890 |
891 | private void createAndSeedCollection(String collectionName)
892 | throws ExecutionException, InterruptedException {
893 | CreateCollection request =
894 | CreateCollection.newBuilder()
895 | .setCollectionName(collectionName)
896 | .setVectorsConfig(
897 | VectorsConfig.newBuilder()
898 | .setParams(
899 | VectorParams.newBuilder().setDistance(Distance.Cosine).setSize(2).build())
900 | .build())
901 | .build();
902 |
903 | client.createCollectionAsync(request).get();
904 |
905 | UpdateResult result =
906 | client
907 | .upsertAsync(
908 | collectionName,
909 | ImmutableList.of(
910 | PointStruct.newBuilder()
911 | .setId(id(8))
912 | .setVectors(VectorsFactory.vectors(ImmutableList.of(3.5f, 4.5f)))
913 | .putAllPayload(
914 | ImmutableMap.of(
915 | "foo", value("hello"),
916 | "bar", value(1),
917 | "date", value("2021-01-01T00:00:00Z")))
918 | .build(),
919 | PointStruct.newBuilder()
920 | .setId(id(9))
921 | .setVectors(VectorsFactory.vectors(ImmutableList.of(10.5f, 11.5f)))
922 | .putAllPayload(
923 | ImmutableMap.of(
924 | "foo", value("goodbye"),
925 | "bar", value(2),
926 | "date", value("2024-01-02T00:00:00Z")))
927 | .build()))
928 | .get();
929 | assertEquals(UpdateStatus.Completed, result.getStatus());
930 | }
931 | }
932 |
--------------------------------------------------------------------------------
/src/test/java/io/qdrant/client/QdrantClientTest.java:
--------------------------------------------------------------------------------
1 | package io.qdrant.client;
2 |
3 | import io.grpc.Grpc;
4 | import io.grpc.InsecureChannelCredentials;
5 | import io.grpc.ManagedChannel;
6 | import org.junit.jupiter.api.AfterEach;
7 | import org.junit.jupiter.api.Assertions;
8 | import org.junit.jupiter.api.BeforeEach;
9 | import org.junit.jupiter.api.Test;
10 | import org.testcontainers.junit.jupiter.Container;
11 | import org.testcontainers.junit.jupiter.Testcontainers;
12 | import org.testcontainers.qdrant.QdrantContainer;
13 |
14 | @Testcontainers
15 | class QdrantClientTest {
16 |
17 | @Container
18 | private static final QdrantContainer QDRANT_CONTAINER =
19 | new QdrantContainer(DockerImage.QDRANT_IMAGE);
20 |
21 | private QdrantClient client;
22 |
23 | @BeforeEach
24 | public void setup() {
25 | ManagedChannel channel =
26 | Grpc.newChannelBuilder(
27 | QDRANT_CONTAINER.getGrpcHostAddress(), InsecureChannelCredentials.create())
28 | .build();
29 | QdrantGrpcClient grpcClient = QdrantGrpcClient.newBuilder(channel, true).build();
30 | client = new QdrantClient(grpcClient);
31 | }
32 |
33 | @AfterEach
34 | public void teardown() {
35 | client.close();
36 | }
37 |
38 | @Test
39 | void canAccessChannelOnGrpcClient() {
40 | Assertions.assertTrue(client.grpcClient().channel().authority().startsWith("localhost"));
41 | }
42 | }
43 |
--------------------------------------------------------------------------------
/src/test/java/io/qdrant/client/QdrantGrpcClientTest.java:
--------------------------------------------------------------------------------
1 | package io.qdrant.client;
2 |
3 | import static org.junit.jupiter.api.Assertions.assertNotNull;
4 |
5 | import io.grpc.Grpc;
6 | import io.grpc.InsecureChannelCredentials;
7 | import io.qdrant.client.grpc.QdrantOuterClass;
8 | import java.util.concurrent.ExecutionException;
9 | import org.junit.jupiter.api.AfterEach;
10 | import org.junit.jupiter.api.BeforeEach;
11 | import org.junit.jupiter.api.Test;
12 | import org.testcontainers.junit.jupiter.Container;
13 | import org.testcontainers.junit.jupiter.Testcontainers;
14 | import org.testcontainers.qdrant.QdrantContainer;
15 |
16 | @Testcontainers
17 | class QdrantGrpcClientTest {
18 |
19 | @Container
20 | private static final QdrantContainer QDRANT_CONTAINER =
21 | new QdrantContainer(DockerImage.QDRANT_IMAGE);
22 |
23 | private QdrantGrpcClient client;
24 |
25 | @BeforeEach
26 | public void setup() {
27 | client =
28 | QdrantGrpcClient.newBuilder(
29 | Grpc.newChannelBuilder(
30 | QDRANT_CONTAINER.getGrpcHostAddress(), InsecureChannelCredentials.create())
31 | .build())
32 | .build();
33 | }
34 |
35 | @AfterEach
36 | public void teardown() {
37 | client.close();
38 | }
39 |
40 | @Test
41 | void healthCheck() throws ExecutionException, InterruptedException {
42 | QdrantOuterClass.HealthCheckReply healthCheckReply =
43 | client.qdrant().healthCheck(QdrantOuterClass.HealthCheckRequest.getDefaultInstance()).get();
44 |
45 | assertNotNull(healthCheckReply.getTitle());
46 | assertNotNull(healthCheckReply.getVersion());
47 | }
48 | }
49 |
--------------------------------------------------------------------------------
/src/test/java/io/qdrant/client/SnapshotsTest.java:
--------------------------------------------------------------------------------
1 | package io.qdrant.client;
2 |
3 | import static org.junit.jupiter.api.Assertions.assertEquals;
4 | import static org.junit.jupiter.api.Assertions.assertThrows;
5 |
6 | import io.grpc.Grpc;
7 | import io.grpc.InsecureChannelCredentials;
8 | import io.grpc.ManagedChannel;
9 | import io.grpc.Status;
10 | import io.grpc.StatusRuntimeException;
11 | import io.qdrant.client.grpc.Collections;
12 | import io.qdrant.client.grpc.SnapshotsService.SnapshotDescription;
13 | import java.util.List;
14 | import java.util.concurrent.ExecutionException;
15 | import java.util.concurrent.TimeUnit;
16 | import org.junit.jupiter.api.AfterEach;
17 | import org.junit.jupiter.api.BeforeEach;
18 | import org.junit.jupiter.api.Test;
19 | import org.junit.jupiter.api.TestInfo;
20 | import org.testcontainers.junit.jupiter.Container;
21 | import org.testcontainers.junit.jupiter.Testcontainers;
22 | import org.testcontainers.qdrant.QdrantContainer;
23 |
24 | @Testcontainers
25 | class SnapshotsTest {
26 | @Container
27 | private static final QdrantContainer QDRANT_CONTAINER =
28 | new QdrantContainer(DockerImage.QDRANT_IMAGE);
29 |
30 | private QdrantClient client;
31 | private ManagedChannel channel;
32 | private String testName;
33 |
34 | @BeforeEach
35 | public void setup(TestInfo testInfo) {
36 | testName = testInfo.getDisplayName().replace("()", "");
37 | channel =
38 | Grpc.newChannelBuilder(
39 | QDRANT_CONTAINER.getGrpcHostAddress(), InsecureChannelCredentials.create())
40 | .build();
41 | QdrantGrpcClient grpcClient = QdrantGrpcClient.newBuilder(channel).build();
42 | client = new QdrantClient(grpcClient);
43 | }
44 |
45 | @AfterEach
46 | public void teardown() throws Exception {
47 | List collectionNames = client.listCollectionsAsync().get();
48 | for (String collectionName : collectionNames) {
49 | List snapshots = client.listSnapshotAsync(collectionName).get();
50 | for (SnapshotDescription snapshot : snapshots) {
51 | client.deleteSnapshotAsync(collectionName, snapshot.getName()).get();
52 | }
53 | client.deleteCollectionAsync(collectionName).get();
54 | }
55 |
56 | List snapshots = client.listFullSnapshotAsync().get();
57 | for (SnapshotDescription snapshot : snapshots) {
58 | client.deleteFullSnapshotAsync(snapshot.getName()).get();
59 | }
60 |
61 | client.close();
62 | channel.shutdownNow().awaitTermination(5, TimeUnit.SECONDS);
63 | }
64 |
65 | @Test
66 | public void createSnapshot() throws ExecutionException, InterruptedException {
67 | createCollection(testName);
68 | client.createSnapshotAsync(testName).get();
69 | }
70 |
71 | @Test
72 | public void deleteSnapshot() throws ExecutionException, InterruptedException {
73 | createCollection(testName);
74 | SnapshotDescription snapshotDescription = client.createSnapshotAsync(testName).get();
75 | client.deleteSnapshotAsync(testName, snapshotDescription.getName()).get();
76 | }
77 |
78 | @Test
79 | public void deleteSnapshot_with_missing_snapshot() {
80 | ExecutionException exception =
81 | assertThrows(
82 | ExecutionException.class,
83 | () -> client.deleteSnapshotAsync(testName, "snapshot_1").get());
84 | Throwable cause = exception.getCause();
85 | assertEquals(StatusRuntimeException.class, cause.getClass());
86 | StatusRuntimeException underlyingException = (StatusRuntimeException) cause;
87 | assertEquals(Status.Code.NOT_FOUND, underlyingException.getStatus().getCode());
88 | }
89 |
90 | @Test
91 | public void listSnapshots() throws ExecutionException, InterruptedException {
92 | createCollection(testName);
93 | client.createSnapshotAsync(testName).get();
94 | // snapshots are timestamped named to second precision. Wait more than 1 second to ensure we get
95 | // 2 snapshots
96 | Thread.sleep(2000);
97 | client.createSnapshotAsync(testName).get();
98 |
99 | List snapshotDescriptions = client.listSnapshotAsync(testName).get();
100 | assertEquals(2, snapshotDescriptions.size());
101 | }
102 |
103 | @Test
104 | public void createFullSnapshot() throws ExecutionException, InterruptedException {
105 | createCollection(testName);
106 | createCollection(testName + "2");
107 | client.createFullSnapshotAsync().get();
108 | }
109 |
110 | @Test
111 | public void deleteFullSnapshot() throws ExecutionException, InterruptedException {
112 | createCollection(testName);
113 | createCollection(testName + "2");
114 | SnapshotDescription snapshotDescription = client.createFullSnapshotAsync().get();
115 | client.deleteFullSnapshotAsync(snapshotDescription.getName()).get();
116 | }
117 |
118 | @Test
119 | public void deleteFullSnapshot_with_missing_snapshot() {
120 | ExecutionException exception =
121 | assertThrows(
122 | ExecutionException.class, () -> client.deleteFullSnapshotAsync("snapshot_1").get());
123 | Throwable cause = exception.getCause();
124 | assertEquals(StatusRuntimeException.class, cause.getClass());
125 | StatusRuntimeException underlyingException = (StatusRuntimeException) cause;
126 | assertEquals(Status.Code.NOT_FOUND, underlyingException.getStatus().getCode());
127 | }
128 |
129 | @Test
130 | public void listFullSnapshots() throws ExecutionException, InterruptedException {
131 | createCollection(testName);
132 | createCollection(testName + 2);
133 | client.createFullSnapshotAsync().get();
134 | // snapshots are timestamped named to second precision. Wait more than 1 second to ensure we get
135 | // 2 snapshots
136 | Thread.sleep(2000);
137 | client.createFullSnapshotAsync().get();
138 |
139 | List snapshotDescriptions = client.listFullSnapshotAsync().get();
140 | assertEquals(2, snapshotDescriptions.size());
141 | }
142 |
143 | private void createCollection(String collectionName)
144 | throws ExecutionException, InterruptedException {
145 | Collections.CreateCollection request =
146 | Collections.CreateCollection.newBuilder()
147 | .setCollectionName(collectionName)
148 | .setVectorsConfig(
149 | Collections.VectorsConfig.newBuilder()
150 | .setParams(
151 | Collections.VectorParams.newBuilder()
152 | .setDistance(Collections.Distance.Cosine)
153 | .setSize(4)
154 | .build())
155 | .build())
156 | .build();
157 |
158 | client.createCollectionAsync(request).get();
159 | }
160 | }
161 |
--------------------------------------------------------------------------------
/src/test/java/io/qdrant/client/VersionsCompatibilityCheckerTest.java:
--------------------------------------------------------------------------------
1 | package io.qdrant.client;
2 |
3 | import static org.junit.jupiter.api.Assertions.assertEquals;
4 | import static org.junit.jupiter.api.Assertions.assertThrows;
5 |
6 | import java.lang.reflect.InvocationTargetException;
7 | import java.lang.reflect.Method;
8 | import java.util.stream.Stream;
9 | import org.junit.jupiter.params.ParameterizedTest;
10 | import org.junit.jupiter.params.provider.MethodSource;
11 |
12 | public class VersionsCompatibilityCheckerTest {
13 | private static Stream validVersionProvider() {
14 | return Stream.of(
15 | new Object[] {"1.2.3", 1, 2},
16 | new Object[] {"1.2.3-alpha", 1, 2},
17 | new Object[] {"1.2", 1, 2},
18 | new Object[] {"1", 1, 0},
19 | new Object[] {"1.", 1, 0});
20 | }
21 |
22 | @ParameterizedTest
23 | @MethodSource("validVersionProvider")
24 | public void testParseVersion_validVersion(String versionStr, int expectedMajor, int expectedMinor)
25 | throws Exception {
26 | Method method =
27 | VersionsCompatibilityChecker.class.getDeclaredMethod("parseVersion", String.class);
28 | method.setAccessible(true);
29 | Version version = (Version) method.invoke(null, versionStr);
30 | assertEquals(expectedMajor, version.getMajor());
31 | assertEquals(expectedMinor, version.getMinor());
32 | }
33 |
34 | private static Stream invalidVersionProvider() {
35 | return Stream.of("v1.12.0", "", ".1", ".1.", "1.null.1", "null.0.1", null);
36 | }
37 |
38 | @ParameterizedTest
39 | @MethodSource("invalidVersionProvider")
40 | public void testParseVersion_invalidVersion(String versionStr) throws Exception {
41 | Method method =
42 | VersionsCompatibilityChecker.class.getDeclaredMethod("parseVersion", String.class);
43 | method.setAccessible(true);
44 | assertThrows(InvocationTargetException.class, () -> method.invoke(null, versionStr));
45 | }
46 |
47 | private static Stream versionCompatibilityProvider() {
48 | return Stream.of(
49 | new Object[] {"1.9.3.dev0", "2.8.1.dev12-something", false},
50 | new Object[] {"1.9", "2.8", false},
51 | new Object[] {"1", "2", false},
52 | new Object[] {"1.9.0", "2.9.0", false},
53 | new Object[] {"1.1.0", "1.2.9", true},
54 | new Object[] {"1.2.7", "1.1.8.dev0", true},
55 | new Object[] {"1.2.1", "1.2.29", true},
56 | new Object[] {"1.2.0", "1.2.0", true},
57 | new Object[] {"1.2.0", "1.4.0", false},
58 | new Object[] {"1.4.0", "1.2.0", false},
59 | new Object[] {"1.9.0", "3.7.0", false},
60 | new Object[] {"3.0.0", "1.0.0", false},
61 | new Object[] {"", "1.0.0", false},
62 | new Object[] {"1.0.0", "", false},
63 | new Object[] {"", "", false});
64 | }
65 |
66 | @ParameterizedTest
67 | @MethodSource("versionCompatibilityProvider")
68 | public void testIsCompatible(String clientVersion, String serverVersion, boolean expected) {
69 | assertEquals(expected, VersionsCompatibilityChecker.isCompatible(clientVersion, serverVersion));
70 | }
71 | }
72 |
--------------------------------------------------------------------------------
/src/test/java/io/qdrant/client/package-info.java:
--------------------------------------------------------------------------------
1 | @ParametersAreNonnullByDefault
2 | package io.qdrant.client;
3 |
4 | import javax.annotation.ParametersAreNonnullByDefault;
5 |
--------------------------------------------------------------------------------