Skip to content

Commit c0f4d32

Browse files
authored
Merge pull request #6 from upstash/update-vectors
feat: add ability to update indexes
2 parents b4be0d1 + 52746de commit c0f4d32

File tree

9 files changed

+420
-0
lines changed

9 files changed

+420
-0
lines changed

src/Contracts/IndexNamespaceInterface.php

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,7 @@
1313
use Upstash\Vector\VectorQuery;
1414
use Upstash\Vector\VectorQueryManyResult;
1515
use Upstash\Vector\VectorQueryResult;
16+
use Upstash\Vector\VectorUpdate;
1617
use Upstash\Vector\VectorUpsert;
1718

1819
interface IndexNamespaceInterface
@@ -54,4 +55,6 @@ public function delete(array $ids): VectorDeleteResult;
5455
public function fetch(VectorFetch $vectorFetch): VectorFetchResult;
5556

5657
public function random(): ?VectorMatch;
58+
59+
public function update(VectorUpdate $update): void;
5760
}

src/Enums/UpdateMode.php

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,9 @@
1+
<?php
2+
3+
namespace Upstash\Vector\Enums;
4+
5+
enum UpdateMode: string
6+
{
7+
case OVERWRITE = 'OVERWRITE';
8+
case PATCH = 'PATCH';
9+
}

src/Index.php

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -130,4 +130,9 @@ public function random(): ?VectorMatch
130130
{
131131
return $this->namespace('')->random();
132132
}
133+
134+
public function update(VectorUpdate $update): void
135+
{
136+
$this->namespace('')->update($update);
137+
}
133138
}

src/IndexNamespace.php

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,7 @@
1313
use Upstash\Vector\Operations\QueryVectorsManyOperation;
1414
use Upstash\Vector\Operations\QueryVectorsOperation;
1515
use Upstash\Vector\Operations\ResetNamespaceOperation;
16+
use Upstash\Vector\Operations\UpdateVectorOperation;
1617
use Upstash\Vector\Operations\UpsertDataOperation;
1718
use Upstash\Vector\Operations\UpsertVectorOperation;
1819

@@ -86,4 +87,9 @@ public function random(): ?VectorMatch
8687
{
8788
return (new FetchRandomVectorOperation($this->namespace, $this->transporter))->fetch();
8889
}
90+
91+
public function update(VectorUpdate $update): void
92+
{
93+
(new UpdateVectorOperation($this->namespace, $this->transporter))->update($update);
94+
}
8995
}
Lines changed: 41 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,41 @@
1+
<?php
2+
3+
namespace Upstash\Vector\Operations;
4+
5+
use Upstash\Vector\Contracts\TransporterInterface;
6+
use Upstash\Vector\Operations\Concerns\AssertsApiResponseErrors;
7+
use Upstash\Vector\Transporter\ContentType;
8+
use Upstash\Vector\Transporter\Method;
9+
use Upstash\Vector\Transporter\TransporterRequest;
10+
use Upstash\Vector\VectorUpdate;
11+
12+
final readonly class UpdateVectorOperation
13+
{
14+
use AssertsApiResponseErrors;
15+
16+
public function __construct(private string $namespace, private TransporterInterface $transporter) {}
17+
18+
public function update(VectorUpdate $update): void
19+
{
20+
$request = new TransporterRequest(
21+
contentType: ContentType::JSON,
22+
method: Method::POST,
23+
path: $this->getPath(),
24+
data: $update->toArray(),
25+
);
26+
27+
$response = $this->transporter->sendRequest($request);
28+
29+
$this->assertResponse($response);
30+
}
31+
32+
private function getPath(): string
33+
{
34+
$namespace = trim($this->namespace);
35+
if ($namespace !== '') {
36+
return "/update/$namespace";
37+
}
38+
39+
return '/update';
40+
}
41+
}

src/VectorUpdate.php

Lines changed: 46 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,46 @@
1+
<?php
2+
3+
namespace Upstash\Vector;
4+
5+
use Upstash\Vector\Enums\UpdateMode;
6+
7+
final readonly class VectorUpdate
8+
{
9+
/**
10+
* @param array<float>|null $vector
11+
*/
12+
public function __construct(
13+
public string $id,
14+
public ?array $vector = null,
15+
public ?SparseVector $sparseVector = null,
16+
public ?string $data = null,
17+
public ?array $metadata = null,
18+
public UpdateMode $metadataUpdateMode = UpdateMode::OVERWRITE,
19+
) {}
20+
21+
public function toArray(): array
22+
{
23+
$data = [
24+
'id' => $this->id,
25+
];
26+
27+
if ($this->vector !== null) {
28+
$data['vector'] = $this->vector;
29+
}
30+
31+
if ($this->sparseVector !== null) {
32+
$data['sparseVector'] = $this->sparseVector->toArray();
33+
}
34+
35+
if ($this->data !== null) {
36+
$data['data'] = $this->data;
37+
}
38+
39+
if ($this->metadata !== null) {
40+
$data['metadata'] = $this->metadata;
41+
$data['metadataUpdateMode'] = $this->metadataUpdateMode->value;
42+
}
43+
44+
return $data;
45+
}
46+
}
Lines changed: 89 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,89 @@
1+
<?php
2+
3+
namespace Upstash\Vector\Tests\Dense\Operations;
4+
5+
use PHPUnit\Framework\TestCase;
6+
use Upstash\Vector\Enums\UpdateMode;
7+
use Upstash\Vector\Tests\Concerns\UsesDenseIndex;
8+
use Upstash\Vector\Tests\Concerns\WaitsForIndex;
9+
use Upstash\Vector\VectorFetch;
10+
use Upstash\Vector\VectorUpdate;
11+
use Upstash\Vector\VectorUpsert;
12+
13+
class UpdateVectorTest extends TestCase
14+
{
15+
use UsesDenseIndex;
16+
use WaitsForIndex;
17+
18+
public function test_can_update_vector(): void
19+
{
20+
// Arrange
21+
$this->namespace->upsertMany([
22+
new VectorUpsert('1', [0.1, 0.1]),
23+
new VectorUpsert('2', [0.2, 0.2]),
24+
new VectorUpsert('3', [0.3, 0.3]),
25+
new VectorUpsert('4', [0.4, 0.4]),
26+
new VectorUpsert('5', [0.5, 0.5]),
27+
]);
28+
29+
$this->waitForIndex($this->namespace);
30+
31+
// Act
32+
$this->namespace->update(new VectorUpdate('1', [0.6, 0.6]));
33+
34+
// Assert
35+
$result = $this->namespace->fetch(new VectorFetch(['1'], includeVectors: true));
36+
$this->assertEquals(1, $result->count());
37+
$this->assertEquals([0.6, 0.6], $result->offsetGet(0)->vector);
38+
}
39+
40+
public function test_can_update_vector_override_metadata(): void
41+
{
42+
// Arrange
43+
$this->namespace->upsert(new VectorUpsert(
44+
id: '1',
45+
vector: [0.1, 0.1],
46+
metadata: ['foo' => 'bar', 'baz' => 'qux'],
47+
));
48+
49+
$this->waitForIndex($this->namespace);
50+
51+
// Act
52+
$this->namespace->update(new VectorUpdate(
53+
id: '1',
54+
metadata: ['foo' => 'baz'],
55+
metadataUpdateMode: UpdateMode::OVERWRITE,
56+
));
57+
58+
// Assert
59+
$result = $this->namespace->fetch(new VectorFetch(['1'], includeMetadata: true, includeVectors: true));
60+
$this->assertEquals(1, $result->count());
61+
$this->assertEquals([0.1, 0.1], $result->offsetGet(0)->vector);
62+
$this->assertEquals(['foo' => 'baz'], $result->offsetGet(0)->metadata);
63+
}
64+
65+
public function test_can_update_vector_patch_metadata(): void
66+
{
67+
// Arrange
68+
$this->namespace->upsert(new VectorUpsert(
69+
id: '1',
70+
vector: [0.1, 0.1],
71+
metadata: ['foo' => 'bar', 'baz' => 'qux'],
72+
));
73+
74+
$this->waitForIndex($this->namespace);
75+
76+
// Act
77+
$this->namespace->update(new VectorUpdate(
78+
id: '1',
79+
metadata: ['foo' => 'baz'],
80+
metadataUpdateMode: UpdateMode::PATCH,
81+
));
82+
83+
// Assert
84+
$result = $this->namespace->fetch(new VectorFetch(['1'], includeMetadata: true, includeVectors: true));
85+
$this->assertEquals(1, $result->count());
86+
$this->assertEquals([0.1, 0.1], $result->offsetGet(0)->vector);
87+
$this->assertEquals(['foo' => 'baz', 'baz' => 'qux'], $result->offsetGet(0)->metadata);
88+
}
89+
}
Lines changed: 114 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,114 @@
1+
<?php
2+
3+
namespace Upstash\Vector\Tests\Hybrid\Operations;
4+
5+
use PHPUnit\Framework\TestCase;
6+
use Upstash\Vector\Enums\UpdateMode;
7+
use Upstash\Vector\SparseVector;
8+
use Upstash\Vector\Tests\Concerns\UsesHybridIndex;
9+
use Upstash\Vector\Tests\Concerns\WaitsForIndex;
10+
use Upstash\Vector\VectorFetch;
11+
use Upstash\Vector\VectorUpdate;
12+
use Upstash\Vector\VectorUpsert;
13+
14+
use function Upstash\Vector\createRandomVector;
15+
16+
class UpdateVectorTest extends TestCase
17+
{
18+
use UsesHybridIndex;
19+
use WaitsForIndex;
20+
21+
public function test_can_update_vector(): void
22+
{
23+
// Arrange
24+
$sparseVector = new SparseVector(
25+
indices: [1, 2, 3],
26+
values: [0.1, 0.2, 0.3],
27+
);
28+
29+
$this->namespace->upsertMany([
30+
new VectorUpsert('1', vector: createRandomVector(dimensions: 384), sparseVector: $sparseVector),
31+
new VectorUpsert('2', vector: createRandomVector(dimensions: 384), sparseVector: $sparseVector),
32+
new VectorUpsert('3', vector: createRandomVector(dimensions: 384), sparseVector: $sparseVector),
33+
new VectorUpsert('4', vector: createRandomVector(dimensions: 384), sparseVector: $sparseVector),
34+
new VectorUpsert('5', vector: createRandomVector(dimensions: 384), sparseVector: $sparseVector),
35+
]);
36+
37+
$this->waitForIndex($this->namespace);
38+
39+
$updatedVector = createRandomVector(dimensions: 384);
40+
41+
// Act
42+
$this->namespace->update(new VectorUpdate(
43+
id: '1',
44+
vector: $updatedVector,
45+
sparseVector: new SparseVector(
46+
indices: [2, 3, 4],
47+
values: [0.6, 0.6, 0.6],
48+
),
49+
));
50+
51+
// Assert
52+
$result = $this->namespace->fetch(new VectorFetch(['1'], includeVectors: true));
53+
$this->assertEquals(1, $result->count());
54+
$this->assertEquals($updatedVector, $result->offsetGet(0)->vector);
55+
$this->assertEquals([2, 3, 4], $result->offsetGet(0)->sparseVector->indices);
56+
$this->assertEquals([0.6, 0.6, 0.6], $result->offsetGet(0)->sparseVector->values);
57+
}
58+
59+
public function test_can_update_vector_override_metadata(): void
60+
{
61+
// Arrange
62+
$this->namespace->upsert(new VectorUpsert(
63+
id: '1',
64+
vector: createRandomVector(dimensions: 384),
65+
sparseVector: new SparseVector(
66+
indices: [1, 2, 3],
67+
values: [0.1, 0.2, 0.3],
68+
),
69+
metadata: ['foo' => 'bar', 'baz' => 'qux'],
70+
));
71+
72+
$this->waitForIndex($this->namespace);
73+
74+
// Act
75+
$this->namespace->update(new VectorUpdate(
76+
id: '1',
77+
metadata: ['foo' => 'baz'],
78+
metadataUpdateMode: UpdateMode::OVERWRITE,
79+
));
80+
81+
// Assert
82+
$result = $this->namespace->fetch(new VectorFetch(['1'], includeMetadata: true, includeVectors: true));
83+
$this->assertEquals(1, $result->count());
84+
$this->assertEquals(['foo' => 'baz'], $result->offsetGet(0)->metadata);
85+
}
86+
87+
public function test_can_update_vector_patch_metadata(): void
88+
{
89+
// Arrange
90+
$this->namespace->upsert(new VectorUpsert(
91+
id: '1',
92+
vector: createRandomVector(dimensions: 384),
93+
sparseVector: new SparseVector(
94+
indices: [1, 2, 3],
95+
values: [0.1, 0.2, 0.3],
96+
),
97+
metadata: ['foo' => 'bar', 'baz' => 'qux'],
98+
));
99+
100+
$this->waitForIndex($this->namespace);
101+
102+
// Act
103+
$this->namespace->update(new VectorUpdate(
104+
id: '1',
105+
metadata: ['foo' => 'baz'],
106+
metadataUpdateMode: UpdateMode::PATCH,
107+
));
108+
109+
// Assert
110+
$result = $this->namespace->fetch(new VectorFetch(['1'], includeMetadata: true, includeVectors: true));
111+
$this->assertEquals(1, $result->count());
112+
$this->assertEquals(['foo' => 'baz', 'baz' => 'qux'], $result->offsetGet(0)->metadata);
113+
}
114+
}

0 commit comments

Comments
 (0)