feat: Severless · qdrant/java-client@10b63eb · GitHub
Skip to content

Commit 10b63eb

Browse files
committed
feat: Severless
1 parent fb8f7ce commit 10b63eb

9 files changed

Lines changed: 1425 additions & 3 deletions

File tree

‎.github/workflows/test.yml‎

Lines changed: 3 additions & 0 deletions

‎CONTRIBUTING.md‎

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -107,6 +107,16 @@ Since the API reference is published at <https://qdrant.github.io/java-client>,
107107

108108
5. Submit your pull request and get those approvals.
109109

110+
### Qdrant Serverless
111+
112+
[`QdrantServerlessClient`](https://github.com/qdrant/java-client/blob/master/src/main/java/io/qdrant/client/QdrantServerlessClient.java) uses a copy of the [serverless collections proto](https://github.com/qdrant/qdrant-cloud-public-api/blob/main/proto/qdrant/serverless/collections.proto) in `src/main/proto`. To update it:
113+
114+
```bash
115+
./gradlew syncServerlessProto
116+
```
117+
118+
Its tests run against a live serverless space when `QDRANT_SERVERLESS_URL` and `QDRANT_SERVERLESS_API_KEY` are set, and are skipped otherwise.
119+
110120
### Releasing a New Version
111121

112122
Once the new Qdrant version is live:

‎build.gradle‎

Lines changed: 38 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -156,6 +156,44 @@ tasks.register('downloadProtos') {
156156
}
157157
}
158158

159+
tasks.register('syncServerlessProto') {
160+
def protoFile = file('src/main/proto/serverless_collections.proto')
161+
outputs.file(protoFile)
162+
outputs.upToDateWhen { false }
163+
164+
doLast {
165+
def url = 'https://raw.githubusercontent.com/qdrant/qdrant-cloud-public-api/main/proto/qdrant/serverless/collections.proto'
166+
String source
167+
try (def httpClient = HttpClients.createDefault()) {
168+
def response = httpClient.execute(new HttpGet(url))
169+
if (response.getStatusLine().getStatusCode() != 200) {
170+
throw new GradleException("failed to download ${url}: ${response.getStatusLine()}")
171+
}
172+
source = response.getEntity().getContent().getText('UTF-8')
173+
}
174+
175+
// buf.validate is server-side only and would need an extra proto dependency. Only plain
176+
// single-option lists are stripped. Anything else is left in place and fails the check.
177+
def javaPackage = 'option java_package = "io.qdrant.client.grpc.serverless";'
178+
source = source
179+
.replace('import "buf/validate/validate.proto";\n', '')
180+
.replaceAll(/\s*\[\(buf\.validate\.[^\[\]"',]*\]/, '')
181+
.replace('package qdrant.serverless;\n', "package qdrant.serverless;\n\n${javaPackage}\n")
182+
if (source.contains('buf.validate') || source.contains('buf/validate') || !source.contains(javaPackage)) {
183+
throw new GradleException('unexpected serverless proto, update syncServerlessProto')
184+
}
185+
186+
def header = '''\
187+
// Source: https://github.com/qdrant/qdrant-cloud-public-api/blob/main/proto/qdrant/serverless/collections.proto
188+
// Renamed because the protobuf registry already has the regular qdrant "collections.proto".
189+
// buf.validate options are stripped: they are server-side only.
190+
// Regenerate with: ./gradlew syncServerlessProto
191+
'''
192+
protoFile.parentFile.mkdirs()
193+
protoFile.text = header + '\n' + source
194+
}
195+
}
196+
159197
processResources {
160198
dependsOn downloadProtos
161199
}

‎src/main/java/io/qdrant/client/QdrantClient.java‎

Lines changed: 55 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -2857,8 +2857,31 @@ public ListenableFuture<Long> countAsync(
28572857
requestBuilder.setFilter(filter);
28582858
}
28592859

2860+
return countAsync(requestBuilder.build(), timeout);
2861+
}
2862+
2863+
/**
2864+
* Count the points in a collection.
2865+
*
2866+
* @param request The count request.
2867+
* @return a new instance of {@link ListenableFuture}
2868+
*/
2869+
public ListenableFuture<Long> countAsync(CountPoints request) {
2870+
return countAsync(request, null);
2871+
}
2872+
2873+
/**
2874+
* Count the points in a collection.
2875+
*
2876+
* @param request The count request.
2877+
* @param timeout The timeout for the call.
2878+
* @return a new instance of {@link ListenableFuture}
2879+
*/
2880+
public ListenableFuture<Long> countAsync(CountPoints request, @Nullable Duration timeout) {
2881+
String collectionName = request.getCollectionName();
2882+
Preconditions.checkArgument(!collectionName.isEmpty(), "Collection name must not be empty");
28602883
logger.debug("Count on '{}'", collectionName);
2861-
ListenableFuture<CountResponse> future = getPoints(timeout).count(requestBuilder.build());
2884+
ListenableFuture<CountResponse> future = getPoints(timeout).count(request);
28622885
addLogFailureCallback(future, "Count");
28632886
return Futures.transform(
28642887
future, response -> response.getResult().getCount(), MoreExecutors.directExecutor());
@@ -2943,9 +2966,34 @@ public ListenableFuture<List<BatchResult>> queryBatchAsync(
29432966
requestBuilder.setReadConsistency(readConsistency);
29442967
}
29452968

2969+
return queryBatchAsync(requestBuilder.build(), timeout);
2970+
}
2971+
2972+
/**
2973+
* Universally query points in batch. Covers all capabilities of search, recommend, discover,
2974+
* filters. Also enables hybrid and multi-stage queries.
2975+
*
2976+
* @param request The batch query request.
2977+
* @return a new instance of {@link ListenableFuture}
2978+
*/
2979+
public ListenableFuture<List<BatchResult>> queryBatchAsync(QueryBatchPoints request) {
2980+
return queryBatchAsync(request, null);
2981+
}
2982+
2983+
/**
2984+
* Universally query points in batch. Covers all capabilities of search, recommend, discover,
2985+
* filters. Also enables hybrid and multi-stage queries.
2986+
*
2987+
* @param request The batch query request.
2988+
* @param timeout The timeout for the call.
2989+
* @return a new instance of {@link ListenableFuture}
2990+
*/
2991+
public ListenableFuture<List<BatchResult>> queryBatchAsync(
2992+
QueryBatchPoints request, @Nullable Duration timeout) {
2993+
String collectionName = request.getCollectionName();
2994+
Preconditions.checkArgument(!collectionName.isEmpty(), "Collection name must not be empty");
29462995
logger.debug("Query batch on '{}'", collectionName);
2947-
ListenableFuture<QueryBatchResponse> future =
2948-
getPoints(timeout).queryBatch(requestBuilder.build());
2996+
ListenableFuture<QueryBatchResponse> future = getPoints(timeout).queryBatch(request);
29492997
addLogFailureCallback(future, "Query batch");
29502998
return Futures.transform(
29512999
future, QueryBatchResponse::getResultList, MoreExecutors.directExecutor());
@@ -3261,6 +3309,10 @@ public void close() {
32613309
}
32623310

32633311
private <V> void addLogFailureCallback(ListenableFuture<V> future, String message) {
3312+
addLogFailureCallback(logger, future, message);
3313+
}
3314+
3315+
static <V> void addLogFailureCallback(Logger logger, ListenableFuture<V> future, String message) {
32643316
Futures.addCallback(
32653317
future,
32663318
new FutureCallback<V>() {

‎src/main/java/io/qdrant/client/QdrantGrpcClient.java‎

Lines changed: 14 additions & 0 deletions

0 commit comments

Comments
 (0)