Skip to content

Commit 776b909

Browse files
Anush008russcam
andauthored
feat: Batch update points (#16)
* feat: batch update points * chore: update filter param doc comments * chore: upadate namedVectors() to accept Map<String, Vector> * chore: typo fix QdrantClient.java * Apply suggestions from code review Co-authored-by: Russ Cam <[email protected]> * chore: review updates * chore: simplify examples README.md * docs: simplify examples README.md * docs: Updated doc URLREADME.md --------- Co-authored-by: Russ Cam <[email protected]>
1 parent b416d22 commit 776b909

File tree

6 files changed

+142
-41
lines changed

6 files changed

+142
-41
lines changed

README.md

Lines changed: 37 additions & 32 deletions
Original file line numberDiff line numberDiff line change
@@ -34,25 +34,26 @@ To install the library, add the following lines to your build config file.
3434
<dependency>
3535
<groupId>io.qdrant</groupId>
3636
<artifactId>client</artifactId>
37-
<version>1.7.0</version>
37+
<version>1.7.1</version>
3838
</dependency>
3939
```
4040

41-
#### Scala SBT
41+
#### SBT
4242

4343
```sbt
44-
libraryDependencies += "io.qdrant" % "client" % "1.7.0"
44+
libraryDependencies += "io.qdrant" % "client" % "1.7.1"
4545
```
4646

4747
#### Gradle
4848

4949
```gradle
50-
implementation 'io.qdrant:client:1.7.0'
50+
implementation 'io.qdrant:client:1.7.1'
5151
```
5252

5353
## 📖 Documentation
5454

55-
- [`QdrantClient` Reference](https://qdrant.github.io/java-client/io/qdrant/client/QdrantClient.html#constructor-detail)
55+
- [JavaDoc Reference](https://qdrant.github.io/java-client/)
56+
- Usage examples are available throughout the [Qdrant documentation](https://qdrant.tech/documentation/quick-start/)
5657

5758
## 🔌 Getting started
5859

@@ -125,39 +126,43 @@ Insert vectors into a collection
125126
// import static convenience methods
126127
import static io.qdrant.client.PointIdFactory.id;
127128
import static io.qdrant.client.ValueFactory.value;
128-
import static io.qdrant.client.VectorsFactory.vector;
129-
130-
Random random = new Random();
131-
List<PointStruct> points = IntStream.range(1, 101)
132-
.mapToObj(i -> PointStruct.newBuilder()
133-
.setId(id(i))
134-
.setVectors(vector(IntStream.range(1, 101)
135-
.mapToObj(v -> random.nextFloat())
136-
.collect(Collectors.toList())))
137-
.putAllPayload(ImmutableMap.of(
138-
"color", value("red"),
139-
"rand_number", value(i % 10))
140-
)
141-
.build()
142-
)
143-
.collect(Collectors.toList());
129+
import static io.qdrant.client.VectorsFactory.vectors;
130+
131+
List<PointStruct> points =
132+
List.of(
133+
PointStruct.newBuilder()
134+
.setId(id(1))
135+
.setVectors(vectors(0.32f, 0.52f, 0.21f, 0.52f))
136+
.putAllPayload(
137+
Map.of(
138+
"color", value("red"),
139+
"rand_number", value(32)))
140+
.build(),
141+
PointStruct.newBuilder()
142+
.setId(id(2))
143+
.setVectors(vectors(0.42f, 0.52f, 0.67f, 0.632f))
144+
.putAllPayload(
145+
Map.of(
146+
"color", value("black"),
147+
"rand_number", value(53),
148+
"extra_field", value(true)))
149+
.build());
144150

145151
UpdateResult updateResult = client.upsertAsync("my_collection", points).get();
146152
```
147153

148154
Search for similar vectors
149155

150156
```java
151-
List<Float> queryVector = IntStream.range(1, 101)
152-
.mapToObj(v -> random.nextFloat())
153-
.collect(Collectors.toList());
154-
155-
List<ScoredPoint> points = client.searchAsync(SearchPoints.newBuilder()
156-
.setCollectionName("my_collection")
157-
.addAllVector(queryVector)
158-
.setLimit(5)
159-
.build()
160-
).get();
157+
List<ScoredPoint> anush =
158+
client
159+
.searchAsync(
160+
SearchPoints.newBuilder()
161+
.setCollectionName("my_collection")
162+
.addAllVector(List.of(0.6235f, 0.123f, 0.532f, 0.123f))
163+
.setLimit(5)
164+
.build())
165+
.get();
161166
```
162167

163168
Search for similar vectors with filtering condition
@@ -168,7 +173,7 @@ import static io.qdrant.client.ConditionFactory.range;
168173

169174
List<ScoredPoint> points = client.searchAsync(SearchPoints.newBuilder()
170175
.setCollectionName("my_collection")
171-
.addAllVector(queryVector)
176+
.addAllVector(List.of(0.6235f, 0.123f, 0.532f, 0.123f))
172177
.setFilter(Filter.newBuilder()
173178
.addMust(range("rand_number", Range.newBuilder().setGte(3).build()))
174179
.build())

build.gradle

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -19,8 +19,8 @@ plugins {
1919
id 'maven-publish'
2020

2121
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"
22+
id 'net.ltgt.errorprone' version '3.1.0'
23+
id 'io.github.gradle-nexus.publish-plugin' version '1.3.0'
2424
}
2525

2626
group = 'io.qdrant'

gradle.properties

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -5,4 +5,4 @@ qdrantProtosVersion=v1.7.0
55
qdrantVersion=v1.7.0
66

77
# The version of the client to generate
8-
packageVersion=1.7.0
8+
packageVersion=1.7.1

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

Lines changed: 68 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -39,6 +39,9 @@
3939
import static io.qdrant.client.grpc.Points.DiscoverBatchResponse;
4040
import static io.qdrant.client.grpc.Points.DiscoverPoints;
4141
import static io.qdrant.client.grpc.Points.DiscoverResponse;
42+
import static io.qdrant.client.grpc.Points.PointsUpdateOperation;
43+
import static io.qdrant.client.grpc.Points.UpdateBatchPoints;
44+
import static io.qdrant.client.grpc.Points.UpdateBatchResponse;
4245
import static io.qdrant.client.grpc.Collections.GetCollectionInfoRequest;
4346
import static io.qdrant.client.grpc.Collections.GetCollectionInfoResponse;
4447
import static io.qdrant.client.grpc.Collections.ListAliasesRequest;
@@ -1551,7 +1554,7 @@ public ListenableFuture<UpdateResult> overwritePayloadAsync(
15511554
}
15521555

15531556
/**
1554-
* Overwrites the payload for the given ids.
1557+
* Overwrites the payload for the filtered points.
15551558
*
15561559
* @param collectionName The name of the collection.
15571560
* @param payload New payload values
@@ -1696,7 +1699,7 @@ public ListenableFuture<UpdateResult> deletePayloadAsync(
16961699
}
16971700

16981701
/**
1699-
* Delete specified key payload for the given ids.
1702+
* Delete specified key payload for the filtered points.
17001703
*
17011704
* @param collectionName The name of the collection.
17021705
* @param keys List of keys to delete.
@@ -1832,7 +1835,7 @@ public ListenableFuture<UpdateResult> clearPayloadAsync(
18321835
}
18331836

18341837
/**
1835-
* Removes all payload for the given ids.
1838+
* Removes all payload for the filtered points.
18361839
*
18371840
* @param collectionName The name of the collection.
18381841
* @param filter A filter selecting the points for which to remove the payload.
@@ -2204,6 +2207,68 @@ public ListenableFuture<List<BatchResult>> recommendBatchAsync(
22042207
MoreExecutors.directExecutor());
22052208
}
22062209

2210+
/**
2211+
* Performs a batch update of points.
2212+
*
2213+
* @param collectionName The name of the collection.
2214+
* @param operations The list of point update operations.
2215+
*
2216+
* @return a new instance of {@link ListenableFuture}
2217+
*/
2218+
public ListenableFuture<List<UpdateResult>> batchUpdateAsync(String collectionName, List<PointsUpdateOperation> operations) {
2219+
return batchUpdateAsync(collectionName, operations, null, null, null);
2220+
}
2221+
2222+
/**
2223+
* Performs a batch update of points.
2224+
*
2225+
* @param collectionName The name of the collection.
2226+
* @param operations The list of point update operations.
2227+
* @param wait Whether to wait until the changes have been applied. Defaults to <code>true</code>.
2228+
* @param ordering Write ordering guarantees.
2229+
* @param timeout The timeout for the call.
2230+
*
2231+
* @return a new instance of {@link ListenableFuture}
2232+
*/
2233+
public ListenableFuture<List<UpdateResult>> batchUpdateAsync(
2234+
String collectionName,
2235+
List<PointsUpdateOperation> operations,
2236+
@Nullable Boolean wait,
2237+
@Nullable WriteOrdering ordering,
2238+
@Nullable Duration timeout) {
2239+
2240+
UpdateBatchPoints.Builder requestBuilder = UpdateBatchPoints.newBuilder()
2241+
.setCollectionName(collectionName)
2242+
.addAllOperations(operations)
2243+
.setWait(wait == null || wait);
2244+
2245+
if (ordering != null) {
2246+
requestBuilder.setOrdering(ordering);
2247+
}
2248+
return batchUpdateAsync(requestBuilder.build(), timeout);
2249+
}
2250+
2251+
2252+
/**
2253+
* Performs a batch update of points.
2254+
*
2255+
* @param request The update batch request.
2256+
* @param timeout The timeout for the call.
2257+
*
2258+
* @return a new instance of {@link ListenableFuture}
2259+
*/
2260+
public ListenableFuture<List<UpdateResult>> batchUpdateAsync(UpdateBatchPoints request, @Nullable Duration timeout) {
2261+
String collectionName = request.getCollectionName();
2262+
Preconditions.checkArgument(!collectionName.isEmpty(), "Collection name must not be empty");
2263+
logger.debug("Batch update points on '{}'", collectionName);
2264+
ListenableFuture<UpdateBatchResponse> future = getPoints(timeout).updateBatch(request);
2265+
addLogFailureCallback(future, "Batch update points");
2266+
return Futures.transform(
2267+
future,
2268+
UpdateBatchResponse::getResultList,
2269+
MoreExecutors.directExecutor());
2270+
}
2271+
22072272
/**
22082273
* Look for the points which are closer to stored positive examples and at the same time further to negative
22092274
* examples, grouped by a given field

src/main/java/io/qdrant/client/VectorsFactory.java

Lines changed: 4 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@
77

88
import static io.qdrant.client.VectorFactory.vector;
99
import static io.qdrant.client.grpc.Points.NamedVectors;
10+
import io.qdrant.client.grpc.Points.Vector;
1011
import static io.qdrant.client.grpc.Points.Vectors;
1112

1213
/**
@@ -18,13 +19,13 @@ private VectorsFactory() {
1819

1920
/**
2021
* Creates named vectors
21-
* @param values A map of vector names to values
22+
* @param values A map of vector names to {@link Vector}
2223
* @return a new instance of {@link Vectors}
2324
*/
24-
public static Vectors namedVectors(Map<String, List<Float>> values) {
25+
public static Vectors namedVectors(Map<String, Vector> values) {
2526
return Vectors.newBuilder()
2627
.setVectors(NamedVectors.newBuilder()
27-
.putAllVectors(Maps.transformValues(values, v -> vector(v)))
28+
.putAllVectors(values)
2829
)
2930
.build();
3031
}

src/test/java/io/qdrant/client/PointsTest.java

Lines changed: 30 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,13 @@
1414
import org.testcontainers.shaded.com.google.common.collect.ImmutableSet;
1515
import io.qdrant.client.container.QdrantContainer;
1616
import io.qdrant.client.grpc.Points.DiscoverPoints;
17+
import io.qdrant.client.grpc.Points.PointVectors;
18+
import io.qdrant.client.grpc.Points.PointsIdsList;
19+
import io.qdrant.client.grpc.Points.PointsSelector;
20+
import io.qdrant.client.grpc.Points.PointsUpdateOperation;
21+
import io.qdrant.client.grpc.Points.UpdateBatchResponse;
22+
import io.qdrant.client.grpc.Points.PointsUpdateOperation.ClearPayload;
23+
import io.qdrant.client.grpc.Points.PointsUpdateOperation.UpdateVectors;
1724

1825
import java.util.List;
1926
import java.util.concurrent.ExecutionException;
@@ -49,6 +56,7 @@
4956
import static io.qdrant.client.TargetVectorFactory.targetVector;
5057
import static io.qdrant.client.ValueFactory.value;
5158
import static io.qdrant.client.VectorFactory.vector;
59+
import static io.qdrant.client.VectorsFactory.vectors;
5260

5361
@Testcontainers
5462
class PointsTest {
@@ -540,6 +548,28 @@ public void delete_by_filter() throws ExecutionException, InterruptedException {
540548
assertEquals(0, points.size());
541549
}
542550

551+
@Test
552+
public void batchPointUpdate() throws ExecutionException, InterruptedException {
553+
createAndSeedCollection(testName);
554+
555+
List<PointsUpdateOperation> operations = List.of(
556+
PointsUpdateOperation.newBuilder()
557+
.setClearPayload(ClearPayload.newBuilder().setPoints(
558+
PointsSelector.newBuilder().setPoints(PointsIdsList.newBuilder().addIds(id(9))))
559+
.build())
560+
.build(),
561+
PointsUpdateOperation.newBuilder()
562+
.setUpdateVectors(UpdateVectors.newBuilder()
563+
.addPoints(PointVectors.newBuilder()
564+
.setId(id(9))
565+
.setVectors(vectors(0.6f, 0.7f))))
566+
.build());
567+
568+
List<UpdateResult> response = client.batchUpdateAsync(testName, operations).get();
569+
570+
response.forEach(result -> assertEquals(UpdateStatus.Completed, result.getStatus()));
571+
}
572+
543573
private void createAndSeedCollection(String collectionName) throws ExecutionException, InterruptedException {
544574
CreateCollection request = CreateCollection.newBuilder()
545575
.setCollectionName(collectionName)

0 commit comments

Comments
 (0)