Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 4 additions & 2 deletions src/Contracts/IndexNamespaceInterface.php
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down Expand Up @@ -51,9 +53,9 @@ public function queryMany(array $queries): VectorQueryManyResult;
public function queryData(DataQuery $query): DataQueryResult;

/**
* @param array<string|VectorIdentifierInterface> $ids
* @param array<string|VectorIdentifierInterface>|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;

Expand Down
2 changes: 1 addition & 1 deletion src/Index.php
Original file line number Diff line number Diff line change
Expand Up @@ -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);
}
Expand Down
5 changes: 4 additions & 1 deletion src/IndexNamespace.php
Original file line number Diff line number Diff line change
Expand Up @@ -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);
Expand Down
54 changes: 38 additions & 16 deletions src/Operations/DeleteVectorsOperation.php
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand All @@ -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;

/**
Expand All @@ -23,19 +26,48 @@
public function __construct(private string $namespace, private TransporterInterface $transporter) {}

/**
* @param array<string|VectorIdentifierInterface> $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<string, mixed> $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');
Expand All @@ -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'] ?? [];
Expand Down
2 changes: 1 addition & 1 deletion src/Transporter/Headers.php
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@
/**
* @internal
*/
class Headers
readonly class Headers
{
/**
* @param array<string, string> $headers
Expand Down
24 changes: 24 additions & 0 deletions src/VectorDeleteByMetadataFilter.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
<?php

namespace Upstash\Vector;

use Upstash\Vector\Contracts\Arrayable;

final readonly class VectorDeleteByMetadataFilter implements Arrayable
{
public function __construct(
public string $filter,
) {}

/**
* @return array{
* filter: string,
* }
*/
public function toArray(): array
{
return [
'filter' => $this->filter,
];
}
}
24 changes: 24 additions & 0 deletions src/VectorDeleteByPrefix.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
<?php

namespace Upstash\Vector;

use Upstash\Vector\Contracts\Arrayable;

final readonly class VectorDeleteByPrefix implements Arrayable
{
public function __construct(
public string $prefix,
) {}

/**
* @return array{
* prefix: string,
* }
*/
public function toArray(): array
{
return [
'prefix' => $this->prefix,
];
}
}
80 changes: 75 additions & 5 deletions tests/Dense/Operations/DeleteVectorsOperationTest.php
Original file line number Diff line number Diff line change
Expand Up @@ -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;

Expand All @@ -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);

Expand All @@ -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);

Expand All @@ -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);
}
}