diff --git a/README.md b/README.md index d402d9a..054fd43 100644 --- a/README.md +++ b/README.md @@ -697,6 +697,86 @@ List values = Arrays.asList(1F, 2F, 3F); UpdateResponse updateResponse = index.update("v1", values, "example-namespace"); ``` +## Fetch vectors by metadata + +The following example fetches vectors by metadata filter. + +```java +import io.pinecone.clients.Index; +import io.pinecone.clients.Pinecone; +import io.pinecone.proto.FetchByMetadataResponse; +import com.google.protobuf.Struct; +import com.google.protobuf.Value; +... + +Pinecone pinecone = new Pinecone.Builder("PINECONE_API_KEY").build(); +Index index = pinecone.getIndexConnection("example-index"); + +// Create a metadata filter +Struct filter = Struct.newBuilder() + .putFields("genre", Value.newBuilder() + .setStructValue(Struct.newBuilder() + .putFields("$eq", Value.newBuilder() + .setStringValue("action") + .build())) + .build()) + .build(); + +// Fetch vectors by metadata with limit +FetchByMetadataResponse response = index.fetchByMetadata("example-namespace", filter, 10, null); + +// Fetch with pagination +String paginationToken = null; +FetchByMetadataResponse fetchResponse = index.fetchByMetadata("example-namespace", filter, 100, paginationToken); + +// Continue pagination if needed +if (fetchResponse.hasPagination() && + fetchResponse.getPagination().getNext() != null && + !fetchResponse.getPagination().getNext().isEmpty()) { + FetchByMetadataResponse nextPage = index.fetchByMetadata( + "example-namespace", filter, 100, fetchResponse.getPagination().getNext()); +} +``` + +## Update vectors by metadata + +The following example updates vectors by metadata filter. + +```java +import io.pinecone.clients.Index; +import io.pinecone.clients.Pinecone; +import io.pinecone.proto.UpdateResponse; +import com.google.protobuf.Struct; +import com.google.protobuf.Value; +... + +Pinecone pinecone = new Pinecone.Builder("PINECONE_API_KEY").build(); +Index index = pinecone.getIndexConnection("example-index"); + +// Create a filter to match vectors +Struct filter = Struct.newBuilder() + .putFields("genre", Value.newBuilder() + .setStructValue(Struct.newBuilder() + .putFields("$eq", Value.newBuilder() + .setStringValue("action") + .build())) + .build()) + .build(); + +// Create new metadata to apply +Struct newMetadata = Struct.newBuilder() + .putFields("updated", Value.newBuilder().setStringValue("true").build()) + .putFields("year", Value.newBuilder().setStringValue("2024").build()) + .build(); + +// Dry run to check how many records would be updated +UpdateResponse dryRunResponse = index.updateByMetadata(filter, newMetadata, "example-namespace", true); +int matchedRecords = dryRunResponse.getMatchedRecords(); + +// Actually perform the update +UpdateResponse updateResponse = index.updateByMetadata(filter, newMetadata, "example-namespace", false); +``` + ## Create namespace The following example shows how to create a namespace. diff --git a/src/integration/java/io/pinecone/integration/dataPlane/FetchAndUpdateByMetadataTest.java b/src/integration/java/io/pinecone/integration/dataPlane/FetchAndUpdateByMetadataTest.java new file mode 100644 index 0000000..4337719 --- /dev/null +++ b/src/integration/java/io/pinecone/integration/dataPlane/FetchAndUpdateByMetadataTest.java @@ -0,0 +1,158 @@ +package io.pinecone.integration.dataPlane; + +import com.google.protobuf.Struct; +import com.google.protobuf.Value; +import io.pinecone.clients.AsyncIndex; +import io.pinecone.clients.Index; +import io.pinecone.helpers.RandomStringBuilder; +import io.pinecone.helpers.TestResourcesManager; +import io.pinecone.proto.FetchByMetadataResponse; +import io.pinecone.proto.UpdateResponse; +import io.pinecone.unsigned_indices_model.VectorWithUnsignedIndices; +import org.junit.jupiter.api.AfterAll; +import org.junit.jupiter.api.BeforeAll; +import org.junit.jupiter.api.Test; + +import java.util.ArrayList; +import java.util.HashMap; +import java.util.List; +import java.util.concurrent.ExecutionException; + +import static io.pinecone.helpers.AssertRetry.assertWithRetry; +import static io.pinecone.helpers.BuildUpsertRequest.*; +import static org.junit.jupiter.api.Assertions.*; + +public class FetchAndUpdateByMetadataTest { + + private static final TestResourcesManager indexManager = TestResourcesManager.getInstance(); + private static Index index; + private static AsyncIndex asyncIndex; + private static final String namespace = RandomStringBuilder.build("ns", 8); + + @BeforeAll + public static void setUp() throws InterruptedException { + int dimension = indexManager.getDimension(); + index = indexManager.getOrCreateServerlessIndexConnection(); + asyncIndex = indexManager.getOrCreateServerlessAsyncIndexConnection(); + + // Upsert vectors with metadata for testing + int numOfVectors = 5; + List upsertIds = getIdsList(numOfVectors); + List vectorsToUpsert = new ArrayList<>(numOfVectors); + + // Upsert vectors with different metadata values + for (int i = 0; i < numOfVectors; i++) { + Struct metadata = generateMetadataStruct(i % 3, (i + 1) % 3); + VectorWithUnsignedIndices vector = new VectorWithUnsignedIndices( + upsertIds.get(i), + generateVectorValuesByDimension(dimension), + metadata, + null + ); + vectorsToUpsert.add(vector); + } + + index.upsert(vectorsToUpsert, namespace); + + // Wait for vectors to be indexed + Thread.sleep(5000); + } + + @AfterAll + public static void cleanUp() { + index.close(); + asyncIndex.close(); + } + + @Test + public void fetchByMetadataSyncTest() throws InterruptedException { + HashMap> metadataMap = createAndGetMetadataMap(); + String filterValue = metadataMap.get(metadataFields[0]).get(0); + + Struct filter = Struct.newBuilder() + .putFields(metadataFields[0], Value.newBuilder() + .setStructValue(Struct.newBuilder() + .putFields("$eq", Value.newBuilder() + .setStringValue(filterValue) + .build())) + .build()) + .build(); + + assertWithRetry(() -> { + FetchByMetadataResponse response = index.fetchByMetadata(namespace, filter, 10, null); + assertNotNull(response); + assertTrue(response.getVectorsCount() > 0); + }, 3); + } + + @Test + public void updateByMetadataSyncTest() throws InterruptedException { + HashMap> metadataMap = createAndGetMetadataMap(); + String filterValue = metadataMap.get(metadataFields[0]).get(0); + + Struct filter = Struct.newBuilder() + .putFields(metadataFields[0], Value.newBuilder() + .setStructValue(Struct.newBuilder() + .putFields("$eq", Value.newBuilder() + .setStringValue(filterValue) + .build())) + .build()) + .build(); + + Struct newMetadata = Struct.newBuilder() + .putFields("updated", Value.newBuilder().setStringValue("true").build()) + .build(); + + assertWithRetry(() -> { + UpdateResponse response = index.updateByMetadata(filter, newMetadata, namespace, false); + assertNotNull(response); + assertTrue(response.getMatchedRecords() > 0); + }, 3); + } + + @Test + public void fetchByMetadataAsyncTest() throws InterruptedException, ExecutionException { + HashMap> metadataMap = createAndGetMetadataMap(); + String filterValue = metadataMap.get(metadataFields[1]).get(0); + + Struct filter = Struct.newBuilder() + .putFields(metadataFields[1], Value.newBuilder() + .setStructValue(Struct.newBuilder() + .putFields("$eq", Value.newBuilder() + .setStringValue(filterValue) + .build())) + .build()) + .build(); + + assertWithRetry(() -> { + FetchByMetadataResponse response = asyncIndex.fetchByMetadata(namespace, filter, 10, null).get(); + assertNotNull(response); + assertTrue(response.getVectorsCount() > 0); + }, 3); + } + + @Test + public void updateByMetadataAsyncTest() throws InterruptedException, ExecutionException { + HashMap> metadataMap = createAndGetMetadataMap(); + String filterValue = metadataMap.get(metadataFields[1]).get(0); + + Struct filter = Struct.newBuilder() + .putFields(metadataFields[1], Value.newBuilder() + .setStructValue(Struct.newBuilder() + .putFields("$eq", Value.newBuilder() + .setStringValue(filterValue) + .build())) + .build()) + .build(); + + Struct newMetadata = Struct.newBuilder() + .putFields("async_updated", Value.newBuilder().setStringValue("true").build()) + .build(); + + assertWithRetry(() -> { + UpdateResponse response = asyncIndex.updateByMetadata(filter, newMetadata, namespace, false).get(); + assertNotNull(response); + assertTrue(response.getMatchedRecords() > 0); + }, 3); + } +} diff --git a/src/main/java/io/pinecone/clients/AsyncIndex.java b/src/main/java/io/pinecone/clients/AsyncIndex.java index 688deb4..08551a2 100644 --- a/src/main/java/io/pinecone/clients/AsyncIndex.java +++ b/src/main/java/io/pinecone/clients/AsyncIndex.java @@ -13,6 +13,8 @@ import io.pinecone.proto.DeleteRequest; import io.pinecone.proto.DescribeIndexStatsRequest; import io.pinecone.proto.FetchResponse; +import io.pinecone.proto.FetchByMetadataRequest; +import io.pinecone.proto.FetchByMetadataResponse; import io.pinecone.proto.ListNamespacesResponse; import io.pinecone.proto.ListResponse; import io.pinecone.proto.MetadataSchema; @@ -638,6 +640,80 @@ public ListenableFuture fetch(List ids, return asyncStub.fetch(fetchRequest); } + /** + * Fetches vectors by metadata filter from a specified namespace. + *

+ * Example: + *

{@code
+     *     import io.pinecone.proto.FetchByMetadataResponse;
+     *     import com.google.protobuf.Struct;
+     *     import com.google.protobuf.Value;
+     *     import com.google.common.util.concurrent.ListenableFuture;
+     *
+     *     ...
+     *
+     *     Struct filter = Struct.newBuilder()
+     *         .putFields("genre", Value.newBuilder().setStringValue("action").build())
+     *         .build();
+     *
+     *     ListenableFuture futureResponse =
+     *     asyncIndex.fetchByMetadata("example-namespace", filter);
+     * }
+ * + * @param namespace The namespace to fetch vectors from. + * @param filter A metadata filter expression. Only vectors matching this filter will be returned. + * @return A {@link ListenableFuture} of {@link FetchByMetadataResponse} containing the fetched vectors that match the filter. + */ + public ListenableFuture fetchByMetadata(String namespace, Struct filter) { + return fetchByMetadata(namespace, filter, null, null); + } + + /** + * Fetches vectors by metadata filter from a specified namespace with optional limit and pagination. + *

+ * Example: + *

{@code
+     *     import io.pinecone.proto.FetchByMetadataResponse;
+     *     import com.google.protobuf.Struct;
+     *     import com.google.protobuf.Value;
+     *     import com.google.common.util.concurrent.ListenableFuture;
+     *     import com.google.common.util.concurrent.Futures;
+     *
+     *     ...
+     *
+     *     Struct filter = Struct.newBuilder()
+     *         .putFields("genre", Value.newBuilder().setStringValue("action").build())
+     *         .build();
+     *
+     *     // Fetch with limit
+     *     ListenableFuture futureResponse =
+     *     asyncIndex.fetchByMetadata("example-namespace", filter, 100, null);
+     *
+     *     // Fetch with pagination
+     *     String paginationToken = null;
+     *     ListenableFuture futureResponse =
+     *     asyncIndex.fetchByMetadata("example-namespace", filter, 100, paginationToken);
+     *     FetchByMetadataResponse fetchResponse = Futures.getUnchecked(futureResponse);
+     *
+     *     // Continue pagination if needed
+     *     if (fetchResponse.hasPagination() && !fetchResponse.getPagination().getNext().isEmpty()) {
+     *         ListenableFuture nextPageFuture =
+     *         asyncIndex.fetchByMetadata("example-namespace", filter, 100, fetchResponse.getPagination().getNext());
+     *     }
+     * }
+ * + * @param namespace The namespace to fetch vectors from. + * @param filter A metadata filter expression. Only vectors matching this filter will be returned. + * @param limit The maximum number of vectors to return. If null, no limit is applied. + * @param paginationToken The token to continue a previous listing operation. If null, starts from the beginning. + * @return A {@link ListenableFuture} of {@link FetchByMetadataResponse} containing the fetched vectors that match the filter. + */ + public ListenableFuture fetchByMetadata(String namespace, Struct filter, Integer limit, String paginationToken) { + FetchByMetadataRequest fetchByMetadataRequest = validateFetchByMetadataRequest(namespace, filter, limit, paginationToken); + + return asyncStub.fetchByMetadata(fetchByMetadataRequest); + } + /** * {@inheritDoc} *

@@ -717,6 +793,79 @@ public ListenableFuture update(String id, return asyncStub.update(updateRequest); } + /** + * {@inheritDoc} + *

+ * Example: + *

{@code
+     *     import io.pinecone.proto.UpdateResponse;
+     *     import com.google.protobuf.Struct;
+     *     import com.google.protobuf.Value;
+     *     import com.google.common.util.concurrent.ListenableFuture;
+     *
+     *     ...
+     *
+     *     Struct filter = Struct.newBuilder()
+     *         .putFields("genre", Value.newBuilder().setStringValue("action").build())
+     *         .build();
+     *
+     *     Struct metadata = Struct.newBuilder()
+     *         .putFields("year", Value.newBuilder().setNumberValue(2020).build())
+     *         .build();
+     *
+     *     ListenableFuture listenableFuture =
+     *     asyncIndex.updateByMetadata(filter, metadata, "example-namespace");
+     * }
+ */ + @Override + public ListenableFuture updateByMetadata(Struct filter, + Struct metadata, + String namespace) { + return updateByMetadata(filter, metadata, namespace, false); + } + + /** + * {@inheritDoc} + *

+ * Example: + *

{@code
+     *     import io.pinecone.proto.UpdateResponse;
+     *     import com.google.protobuf.Struct;
+     *     import com.google.protobuf.Value;
+     *     import com.google.common.util.concurrent.ListenableFuture;
+     *     import com.google.common.util.concurrent.Futures;
+     *
+     *     ...
+     *
+     *     Struct filter = Struct.newBuilder()
+     *         .putFields("genre", Value.newBuilder().setStringValue("action").build())
+     *         .build();
+     *
+     *     Struct metadata = Struct.newBuilder()
+     *         .putFields("year", Value.newBuilder().setNumberValue(2020).build())
+     *         .build();
+     *
+     *     // Dry run to check how many records would be updated
+     *     ListenableFuture futureResponse =
+     *     asyncIndex.updateByMetadata(filter, metadata, "example-namespace", true);
+     *     UpdateResponse updateResponse = Futures.getUnchecked(futureResponse);
+     *     int matchedRecords = updateResponse.getMatchedRecords();
+     *
+     *     // Actually perform the update
+     *     ListenableFuture actualFutureResponse =
+     *     asyncIndex.updateByMetadata(filter, metadata, "example-namespace", false);
+     * }
+ */ + @Override + public ListenableFuture updateByMetadata(Struct filter, + Struct metadata, + String namespace, + boolean dryRun) { + UpdateRequest updateRequest = validateUpdateByMetadataRequest(filter, metadata, namespace, dryRun); + + return asyncStub.update(updateRequest); + } + /** * {@inheritDoc} *

diff --git a/src/main/java/io/pinecone/clients/Index.java b/src/main/java/io/pinecone/clients/Index.java index 48c5adb..53102bb 100644 --- a/src/main/java/io/pinecone/clients/Index.java +++ b/src/main/java/io/pinecone/clients/Index.java @@ -10,6 +10,8 @@ import io.pinecone.proto.DeleteRequest; import io.pinecone.proto.DescribeIndexStatsRequest; import io.pinecone.proto.FetchResponse; +import io.pinecone.proto.FetchByMetadataRequest; +import io.pinecone.proto.FetchByMetadataResponse; import io.pinecone.proto.ListNamespacesResponse; import io.pinecone.proto.ListResponse; import io.pinecone.proto.MetadataSchema; @@ -585,6 +587,72 @@ public FetchResponse fetch(List ids, return blockingStub.fetch(fetchRequest); } + /** + * Fetches vectors by metadata filter from a specified namespace. + *

+ * Example: + *

{@code
+     *     import io.pinecone.proto.FetchByMetadataResponse;
+     *     import com.google.protobuf.Struct;
+     *     import com.google.protobuf.Value;
+     *
+     *     ...
+     *
+     *     Struct filter = Struct.newBuilder()
+     *         .putFields("genre", Value.newBuilder().setStringValue("action").build())
+     *         .build();
+     *
+     *     FetchByMetadataResponse fetchResponse = index.fetchByMetadata("example-namespace", filter);
+     * }
+ * + * @param namespace The namespace to fetch vectors from. + * @param filter A metadata filter expression. Only vectors matching this filter will be returned. + * @return A {@link FetchByMetadataResponse} containing the fetched vectors that match the filter. + */ + public FetchByMetadataResponse fetchByMetadata(String namespace, Struct filter) { + return fetchByMetadata(namespace, filter, null, null); + } + + /** + * Fetches vectors by metadata filter from a specified namespace with optional limit and pagination. + *

+ * Example: + *

{@code
+     *     import io.pinecone.proto.FetchByMetadataResponse;
+     *     import com.google.protobuf.Struct;
+     *     import com.google.protobuf.Value;
+     *
+     *     ...
+     *
+     *     Struct filter = Struct.newBuilder()
+     *         .putFields("genre", Value.newBuilder().setStringValue("action").build())
+     *         .build();
+     *
+     *     // Fetch with limit
+     *     FetchByMetadataResponse fetchResponse = index.fetchByMetadata("example-namespace", filter, 100, null);
+     *
+     *     // Fetch with pagination
+     *     String paginationToken = null;
+     *     FetchByMetadataResponse fetchResponse = index.fetchByMetadata("example-namespace", filter, 100, paginationToken);
+     *
+     *     // Continue pagination if needed
+     *     if (fetchResponse.hasPagination() && !fetchResponse.getPagination().getNext().isEmpty()) {
+     *         FetchByMetadataResponse nextPage = index.fetchByMetadata("example-namespace", filter, 100, fetchResponse.getPagination().getNext());
+     *     }
+     * }
+ * + * @param namespace The namespace to fetch vectors from. + * @param filter A metadata filter expression. Only vectors matching this filter will be returned. + * @param limit The maximum number of vectors to return. If null, no limit is applied. + * @param paginationToken The token to continue a previous listing operation. If null, starts from the beginning. + * @return A {@link FetchByMetadataResponse} containing the fetched vectors that match the filter. + */ + public FetchByMetadataResponse fetchByMetadata(String namespace, Struct filter, Integer limit, String paginationToken) { + FetchByMetadataRequest fetchByMetadataRequest = validateFetchByMetadataRequest(namespace, filter, limit, paginationToken); + + return blockingStub.fetchByMetadata(fetchByMetadataRequest); + } + /** * {@inheritDoc} *

@@ -662,6 +730,75 @@ public UpdateResponse update(String id, return blockingStub.update(updateRequest); } + /** + * {@inheritDoc} + *

+ * Example: + *

{@code
+     *     import io.pinecone.proto.UpdateResponse;
+     *     import com.google.protobuf.Struct;
+     *     import com.google.protobuf.Value;
+     *
+     *     ...
+     *
+     *     Struct filter = Struct.newBuilder()
+     *         .putFields("genre", Value.newBuilder().setStringValue("action").build())
+     *         .build();
+     *
+     *     Struct metadata = Struct.newBuilder()
+     *         .putFields("year", Value.newBuilder().setNumberValue(2020).build())
+     *         .build();
+     *
+     *     UpdateResponse updateResponse =
+     *     index.updateByMetadata(filter, metadata, "example-namespace");
+     * }
+ */ + @Override + public UpdateResponse updateByMetadata(Struct filter, + Struct metadata, + String namespace) { + return updateByMetadata(filter, metadata, namespace, false); + } + + /** + * {@inheritDoc} + *

+ * Example: + *

{@code
+     *     import io.pinecone.proto.UpdateResponse;
+     *     import com.google.protobuf.Struct;
+     *     import com.google.protobuf.Value;
+     *
+     *     ...
+     *
+     *     Struct filter = Struct.newBuilder()
+     *         .putFields("genre", Value.newBuilder().setStringValue("action").build())
+     *         .build();
+     *
+     *     Struct metadata = Struct.newBuilder()
+     *         .putFields("year", Value.newBuilder().setNumberValue(2020).build())
+     *         .build();
+     *
+     *     // Dry run to check how many records would be updated
+     *     UpdateResponse updateResponse =
+     *     index.updateByMetadata(filter, metadata, "example-namespace", true);
+     *     int matchedRecords = updateResponse.getMatchedRecords();
+     *
+     *     // Actually perform the update
+     *     UpdateResponse actualUpdateResponse =
+     *     index.updateByMetadata(filter, metadata, "example-namespace", false);
+     * }
+ */ + @Override + public UpdateResponse updateByMetadata(Struct filter, + Struct metadata, + String namespace, + boolean dryRun) { + UpdateRequest updateRequest = validateUpdateByMetadataRequest(filter, metadata, namespace, dryRun); + + return blockingStub.update(updateRequest); + } + /** * {@inheritDoc} *

diff --git a/src/main/java/io/pinecone/commons/IndexInterface.java b/src/main/java/io/pinecone/commons/IndexInterface.java index ea8e9ac..cc1d9a4 100644 --- a/src/main/java/io/pinecone/commons/IndexInterface.java +++ b/src/main/java/io/pinecone/commons/IndexInterface.java @@ -3,6 +3,8 @@ import com.google.protobuf.Struct; import io.pinecone.exceptions.PineconeValidationException; import io.pinecone.proto.*; +import io.pinecone.proto.FetchByMetadataRequest; +import io.pinecone.proto.FetchByMetadataResponse; import io.pinecone.unsigned_indices_model.SparseValuesWithUnsignedIndices; import io.pinecone.unsigned_indices_model.VectorWithUnsignedIndices; @@ -277,6 +279,42 @@ default FetchRequest validateFetchRequest(List ids, String namespace) { return fetchRequest.build(); } + /** + * Validates and constructs a fetch by metadata request for retrieving vectors that match a metadata filter. + * + * @param namespace The namespace to fetch vectors from. + * @param filter A metadata filter expression. Only vectors matching this filter will be returned. + * @param limit The maximum number of vectors to return. + * @param paginationToken The token to continue a previous listing operation. + * @return A {@link FetchByMetadataRequest} object constructed from the provided parameters. + * @throws PineconeValidationException if the namespace is not provided. + */ + default FetchByMetadataRequest validateFetchByMetadataRequest(String namespace, + Struct filter, + Integer limit, + String paginationToken) { + FetchByMetadataRequest.Builder fetchByMetadataRequest = FetchByMetadataRequest.newBuilder(); + + if (namespace == null || namespace.isEmpty()) { + throw new PineconeValidationException("Invalid fetch by metadata request. Namespace must be present"); + } + fetchByMetadataRequest.setNamespace(namespace); + + if (filter != null) { + fetchByMetadataRequest.setFilter(filter); + } + + if (limit != null && limit > 0) { + fetchByMetadataRequest.setLimit(limit); + } + + if (paginationToken != null && !paginationToken.isEmpty()) { + fetchByMetadataRequest.setPaginationToken(paginationToken); + } + + return fetchByMetadataRequest.build(); + } + /** * Validates and constructs an update request for modifying an existing vector. * @@ -332,6 +370,44 @@ default UpdateRequest validateUpdateRequest(String id, return updateRequest.build(); } + /** + * Validates and constructs an update request for modifying vectors by metadata filter. + * + * @param filter A metadata filter expression. When updating metadata across records in a namespace, + * the update is applied to all records that match the filter. + * @param metadata The new metadata to set for the vectors. If provided, it replaces the existing metadata. + * @param namespace The namespace containing the records to update. + * @param dryRun If true, return the number of records that match the filter, but do not execute the update. + * Default is false. + * @return An {@link UpdateRequest} object constructed from the provided parameters. + * @throws PineconeValidationException if there are invalid arguments. + */ + default UpdateRequest validateUpdateByMetadataRequest(Struct filter, + Struct metadata, + String namespace, + Boolean dryRun) { + UpdateRequest.Builder updateRequest = UpdateRequest.newBuilder(); + + if (filter == null) { + throw new PineconeValidationException("Invalid update by metadata request. Filter must be present"); + } + updateRequest.setFilter(filter); + + if (metadata != null) { + updateRequest.setSetMetadata(metadata); + } + + if (namespace != null) { + updateRequest.setNamespace(namespace); + } + + if (dryRun != null) { + updateRequest.setDryRun(dryRun); + } + + return updateRequest.build(); + } + /** * Validates and constructs a delete request for removing vectors by their IDs or based on a filter. * Can also handle requests for deleting all vectors within a namespace. @@ -596,6 +672,7 @@ U query(int topK, List vector, List sparseIndices, List spar */ V fetch(List ids, String namespace); + /** * Updates an existing vector by ID with new values in the default namespace. * @@ -630,6 +707,29 @@ U query(int topK, List vector, List sparseIndices, List spar W update(String id, List values, Struct metadata, String namespace, List sparseIndices, List sparseValues); + /** + * Updates vectors by metadata filter in a specified namespace. The update is applied to all records that match the filter. + * + * @param filter A metadata filter expression. The update is applied to all records that match the filter. + * @param metadata The new metadata to set for the vectors. If provided, it replaces the existing metadata. + * @param namespace The namespace containing the records to update. + * @return A generic type {@code W} representing the result of the update operation. + */ + W updateByMetadata(Struct filter, Struct metadata, String namespace); + + /** + * Updates vectors by metadata filter in a specified namespace with optional dry run mode. + * The update is applied to all records that match the filter. + * + * @param filter A metadata filter expression. The update is applied to all records that match the filter. + * @param metadata The new metadata to set for the vectors. If provided, it replaces the existing metadata. + * @param namespace The namespace containing the records to update. + * @param dryRun If true, return the number of records that match the filter, but do not execute the update. + * Default is false. + * @return A generic type {@code W} representing the result of the update operation. + */ + W updateByMetadata(Struct filter, Struct metadata, String namespace, boolean dryRun); + /** * Deletes a list of vectors by ID from the default namespace. *