diff --git a/src/Contracts/IndexNamespaceInterface.php b/src/Contracts/IndexNamespaceInterface.php index 78c508c..da74104 100644 --- a/src/Contracts/IndexNamespaceInterface.php +++ b/src/Contracts/IndexNamespaceInterface.php @@ -7,6 +7,8 @@ use Upstash\Vector\DataUpsert; use Upstash\Vector\Iterators\VectorRangeIterator; use Upstash\Vector\NamespaceInfo; +use Upstash\Vector\VectorDeleteByMetadataFilter; +use Upstash\Vector\VectorDeleteByPrefix; use Upstash\Vector\VectorDeleteResult; use Upstash\Vector\VectorFetch; use Upstash\Vector\VectorFetchResult; @@ -51,9 +53,9 @@ public function queryMany(array $queries): VectorQueryManyResult; public function queryData(DataQuery $query): DataQueryResult; /** - * @param array $ids + * @param array|string|VectorDeleteByPrefix|VectorDeleteByMetadataFilter $ids */ - public function delete(array $ids): VectorDeleteResult; + public function delete(array|string|VectorDeleteByPrefix|VectorDeleteByMetadataFilter $ids): VectorDeleteResult; public function fetch(VectorFetch $vectorFetch): VectorFetchResult; diff --git a/src/Index.php b/src/Index.php index 11b9ec3..e5245c2 100755 --- a/src/Index.php +++ b/src/Index.php @@ -113,7 +113,7 @@ public function queryData(DataQuery $query): DataQueryResult return $this->namespace('')->queryData($query); } - public function delete(array $ids): VectorDeleteResult + public function delete(array|string|VectorDeleteByPrefix|VectorDeleteByMetadataFilter $ids): VectorDeleteResult { return $this->namespace('')->delete($ids); } diff --git a/src/IndexNamespace.php b/src/IndexNamespace.php index ea379c5..2e4815e 100644 --- a/src/IndexNamespace.php +++ b/src/IndexNamespace.php @@ -73,7 +73,10 @@ public function queryData(DataQuery $query): DataQueryResult return (new QueryDataOperation($this->namespace, $this->transporter))->query($query); } - public function delete(array $ids): VectorDeleteResult + /** + * @param string[]|string|VectorDeleteByPrefix|VectorDeleteByMetadataFilter $ids + */ + public function delete(array|string|VectorDeleteByPrefix|VectorDeleteByMetadataFilter $ids): VectorDeleteResult { return (new DeleteVectorsOperation($this->namespace, $this->transporter)) ->delete($ids); diff --git a/src/Operations/DeleteVectorsOperation.php b/src/Operations/DeleteVectorsOperation.php index 536de0f..ea0e70f 100644 --- a/src/Operations/DeleteVectorsOperation.php +++ b/src/Operations/DeleteVectorsOperation.php @@ -3,6 +3,7 @@ namespace Upstash\Vector\Operations; use InvalidArgumentException; +use Upstash\Vector\Contracts\Arrayable; use Upstash\Vector\Contracts\TransporterInterface; use Upstash\Vector\Contracts\VectorIdentifierInterface; use Upstash\Vector\Exceptions\OperationFailedException; @@ -11,6 +12,8 @@ use Upstash\Vector\Transporter\Method; use Upstash\Vector\Transporter\TransporterRequest; use Upstash\Vector\Transporter\TransporterResponse; +use Upstash\Vector\VectorDeleteByMetadataFilter; +use Upstash\Vector\VectorDeleteByPrefix; use Upstash\Vector\VectorDeleteResult; /** @@ -23,19 +26,48 @@ public function __construct(private string $namespace, private TransporterInterface $transporter) {} /** - * @param array $ids + * @param string[]|string|VectorDeleteByPrefix|VectorDeleteByMetadataFilter $ids + * + * @throws OperationFailedException */ - public function delete(array $ids): VectorDeleteResult + public function delete(array|string|VectorDeleteByPrefix|VectorDeleteByMetadataFilter $ids): VectorDeleteResult { - $path = $this->getPath(); - $vectorIds = $this->mapIds($ids); + if ($ids instanceof Arrayable) { + return $this->sendDeleteRequest($ids->toArray()); + } + + if (is_string($ids)) { + return $this->sendDeleteRequest([ + 'ids' => [$ids], + ]); + } + + return $this->sendDeleteRequest([ + 'ids' => $this->mapIds($ids), + ]); + } + + private function getPath(): string + { + $namespace = trim($this->namespace); + if ($namespace === '') { + return '/delete'; + } + + return "/delete/$namespace"; + } + /** + * @param array $payload + */ + private function sendDeleteRequest(array $payload): VectorDeleteResult + { try { $request = new TransporterRequest( contentType: ContentType::JSON, method: Method::DELETE, - path: $path, - data: $vectorIds, + path: $this->getPath(), + data: $payload, ); } catch (\JsonException $e) { throw new OperationFailedException('Invalid JSON'); @@ -48,16 +80,6 @@ public function delete(array $ids): VectorDeleteResult return $this->transformResponse($response); } - private function getPath(): string - { - $namespace = trim($this->namespace); - if ($namespace === '') { - return '/delete'; - } - - return "/delete/$namespace"; - } - private function transformResponse(TransporterResponse $response): VectorDeleteResult { $data = json_decode($response->data, true)['result'] ?? []; diff --git a/src/Transporter/Headers.php b/src/Transporter/Headers.php index cc424f5..8fcbd08 100644 --- a/src/Transporter/Headers.php +++ b/src/Transporter/Headers.php @@ -5,7 +5,7 @@ /** * @internal */ -class Headers +readonly class Headers { /** * @param array $headers diff --git a/src/VectorDeleteByMetadataFilter.php b/src/VectorDeleteByMetadataFilter.php new file mode 100644 index 0000000..1c69ad8 --- /dev/null +++ b/src/VectorDeleteByMetadataFilter.php @@ -0,0 +1,24 @@ + $this->filter, + ]; + } +} diff --git a/src/VectorDeleteByPrefix.php b/src/VectorDeleteByPrefix.php new file mode 100644 index 0000000..b31c656 --- /dev/null +++ b/src/VectorDeleteByPrefix.php @@ -0,0 +1,24 @@ + $this->prefix, + ]; + } +} diff --git a/tests/Dense/Operations/DeleteVectorsOperationTest.php b/tests/Dense/Operations/DeleteVectorsOperationTest.php index e07f096..6316947 100644 --- a/tests/Dense/Operations/DeleteVectorsOperationTest.php +++ b/tests/Dense/Operations/DeleteVectorsOperationTest.php @@ -5,6 +5,8 @@ use PHPUnit\Framework\TestCase; use Upstash\Vector\Tests\Concerns\UsesDenseIndex; use Upstash\Vector\Tests\Concerns\WaitsForIndex; +use Upstash\Vector\VectorDeleteByMetadataFilter; +use Upstash\Vector\VectorDeleteByPrefix; use Upstash\Vector\VectorQuery; use Upstash\Vector\VectorUpsert; @@ -18,9 +20,9 @@ class DeleteVectorsOperationTest extends TestCase public function test_delete_vectors(): void { $this->namespace->upsertMany([ - new VectorUpsert('id-1', createRandomVector(2)), - new VectorUpsert('id-2', createRandomVector(2)), - new VectorUpsert('id-3', createRandomVector(2)), + new VectorUpsert('id-1', vector: createRandomVector(2)), + new VectorUpsert('id-2', vector: createRandomVector(2)), + new VectorUpsert('id-3', vector: createRandomVector(2)), ]); $this->waitForIndex($this->namespace); @@ -34,13 +36,29 @@ public function test_delete_vectors(): void $this->assertSame(1, $info->vectorCount); } + public function test_delete_single_vector(): void + { + $this->namespace->upsertMany([ + new VectorUpsert('id-1', vector: createRandomVector(2)), + new VectorUpsert('id-2', vector: createRandomVector(2)), + new VectorUpsert('id-3', vector: createRandomVector(2)), + ]); + $this->waitForIndex($this->namespace); + + $result = $this->namespace->delete('id-1'); + + $this->assertEquals(1, $result->deleted); + $info = $this->namespace->getNamespaceInfo(); + $this->assertSame(2, $info->vectorCount); + } + public function test_delete_vectors_from_a_query_result_results(): void { $vector = createRandomVector(2); $this->namespace->upsertMany([ new VectorUpsert('id-1', $vector), - new VectorUpsert('id-2', createRandomVector(2)), - new VectorUpsert('id-3', createRandomVector(2)), + new VectorUpsert('id-2', vector: createRandomVector(2)), + new VectorUpsert('id-3', vector: createRandomVector(2)), ]); $this->waitForIndex($this->namespace); @@ -53,4 +71,56 @@ public function test_delete_vectors_from_a_query_result_results(): void $this->assertEquals(2, $result->deleted); } + + public function test_delete_vectors_using_an_id_prefix(): void + { + $this->namespace->upsertMany([ + new VectorUpsert('users:1', vector: createRandomVector(2)), + new VectorUpsert('users:2', vector: createRandomVector(2)), + new VectorUpsert('posts:1', vector: createRandomVector(2)), + ]); + $this->waitForIndex($this->namespace); + + $result = $this->namespace->delete(new VectorDeleteByPrefix( + prefix: 'users:', + )); + + $this->assertEquals(2, $result->deleted); + $this->assertEquals(1, $this->namespace->getNamespaceInfo()->vectorCount); + } + + public function test_delete_vectors_using_a_metadata_filter(): void + { + $this->namespace->upsertMany([ + new VectorUpsert( + id: 'users:1', + vector: createRandomVector(2), + metadata: [ + 'salary' => 1000, + ], + ), + new VectorUpsert( + id: 'users:2', + vector: createRandomVector(2), + metadata: [ + 'salary' => 2000, + ], + ), + new VectorUpsert( + id: 'users:3', + vector: createRandomVector(2), + metadata: [ + 'salary' => 3000, + ], + ), + ]); + $this->waitForIndex($this->namespace); + + $result = $this->namespace->delete(new VectorDeleteByMetadataFilter( + filter: 'salary > 1000', + )); + + $this->assertEquals(2, $result->deleted); + $this->assertEquals(1, $this->namespace->getNamespaceInfo()->vectorCount); + } }