diff --git a/.github/workflows/run-tests.yml b/.github/workflows/run-tests.yml
index 6333ab2..15ffbaa 100644
--- a/.github/workflows/run-tests.yml
+++ b/.github/workflows/run-tests.yml
@@ -16,6 +16,8 @@ env:
DENSE_EMBEDDING_UPSTASH_VECTOR_REST_TOKEN: ${{ secrets.DENSE_EMBEDDING_UPSTASH_VECTOR_REST_TOKEN }}
SPARSE_UPSTASH_VECTOR_REST_URL: ${{ secrets.SPARSE_UPSTASH_VECTOR_REST_URL }}
SPARSE_UPSTASH_VECTOR_REST_TOKEN: ${{ secrets.SPARSE_UPSTASH_VECTOR_REST_TOKEN }}
+ HYBRID_UPSTASH_VECTOR_REST_URL: ${{ secrets.HYBRID_UPSTASH_VECTOR_REST_URL }}
+ HYBRID_UPSTASH_VECTOR_REST_TOKEN: ${{ secrets.HYBRID_UPSTASH_VECTOR_REST_TOKEN }}
jobs:
test:
diff --git a/phpunit.xml.dist b/phpunit.xml.dist
index a81ea2a..92e9f88 100644
--- a/phpunit.xml.dist
+++ b/phpunit.xml.dist
@@ -52,5 +52,8 @@
+
+
+
diff --git a/tests/Concerns/GeneratesVectors.php b/tests/Concerns/GeneratesVectors.php
index edaaebd..30651c6 100644
--- a/tests/Concerns/GeneratesVectors.php
+++ b/tests/Concerns/GeneratesVectors.php
@@ -2,4 +2,16 @@
namespace Upstash\Vector\Tests\Concerns;
-trait GeneratesVectors {}
+trait GeneratesVectors
+{
+ protected function generateVector(int $dimensions): array
+ {
+ $vector = [];
+
+ for ($i = 0; $i < $dimensions; $i++) {
+ $vector[] = random_int(0, 100) / 100;
+ }
+
+ return $vector;
+ }
+}
diff --git a/tests/Concerns/UsesHybridIndex.php b/tests/Concerns/UsesHybridIndex.php
new file mode 100644
index 0000000..2cae894
--- /dev/null
+++ b/tests/Concerns/UsesHybridIndex.php
@@ -0,0 +1,33 @@
+index = new Index(
+ url: getenv('HYBRID_UPSTASH_VECTOR_REST_URL'),
+ token: getenv('HYBRID_UPSTASH_VECTOR_REST_TOKEN'),
+ );
+
+ $this->namespace = $this->index->namespace(bin2hex(random_bytes(32)));
+ }
+
+ public function tearDown(): void
+ {
+ $this->namespace->delete();
+
+ parent::tearDown();
+ }
+}
diff --git a/tests/Hybrid/Operations/QueryDataTest.php b/tests/Hybrid/Operations/QueryDataTest.php
new file mode 100644
index 0000000..a940e21
--- /dev/null
+++ b/tests/Hybrid/Operations/QueryDataTest.php
@@ -0,0 +1,43 @@
+namespace->upsertDataMany([
+ new DataUpsert(
+ id: '1',
+ data: 'The capital of Japan is Tokyo',
+ ),
+ new DataUpsert(
+ id: '2',
+ data: 'The capital of France is Paris',
+ ),
+ new DataUpsert(
+ id: '3',
+ data: 'The capital of Germany is Berlin',
+ ),
+ ]);
+
+ $this->waitForIndex($this->namespace);
+
+ $results = $this->namespace->queryData(new DataQuery(
+ data: 'capital of France',
+ topK: 1,
+ ));
+
+ $this->assertCount(1, $results);
+ $this->assertEquals('2', $results[0]->id);
+ }
+}
diff --git a/tests/Hybrid/Operations/QueryVectorsTest.php b/tests/Hybrid/Operations/QueryVectorsTest.php
new file mode 100644
index 0000000..b1f74d6
--- /dev/null
+++ b/tests/Hybrid/Operations/QueryVectorsTest.php
@@ -0,0 +1,95 @@
+generateVector(384);
+
+ $this->namespace->upsert(new VectorUpsert(
+ id: '1',
+ vector: $vector,
+ sparseVector: new SparseVector(
+ indices: [1, 2, 3],
+ values: [5, 6, 7],
+ ),
+ ));
+
+ $this->waitForIndex($this->namespace);
+
+ $vectors = $this->namespace->query(new VectorQuery(
+ vector: $vector,
+ sparseVector: new SparseVector(
+ indices: [1, 2, 3],
+ values: [5, 6, 7],
+ ),
+ topK: 1,
+ ));
+
+ $this->assertCount(1, $vectors);
+ $this->assertSame('1', $vectors[0]->id);
+ }
+
+ public function test_query_with_no_vectors(): void
+ {
+ $this->namespace->upsert(new VectorUpsert(
+ id: '1',
+ vector: $this->generateVector(384),
+ sparseVector: new SparseVector(
+ indices: [1, 2, 3],
+ values: [5, 6, 7],
+ ),
+ ));
+
+ $this->waitForIndex($this->namespace);
+
+ $vectors = $this->namespace->query(new VectorQuery(
+ sparseVector: new SparseVector(
+ indices: [1, 2, 3],
+ values: [5, 6, 7],
+ ),
+ topK: 1,
+ ));
+
+ $this->assertCount(1, $vectors);
+ $this->assertSame('1', $vectors[0]->id);
+ }
+
+ public function test_query_with_no_sparse_vectors(): void
+ {
+ $vector = $this->generateVector(384);
+
+ $this->namespace->upsert(new VectorUpsert(
+ id: '1',
+ vector: $vector,
+ sparseVector: new SparseVector(
+ indices: [1, 2, 3],
+ values: [5, 6, 7],
+ ),
+ ));
+
+ $this->waitForIndex($this->namespace);
+
+ $vectors = $this->namespace->query(new VectorQuery(
+ vector: $vector,
+ topK: 1,
+ ));
+
+ $this->assertCount(1, $vectors);
+ $this->assertSame('1', $vectors[0]->id);
+ }
+}
diff --git a/tests/Hybrid/Operations/UpsertDataTest.php b/tests/Hybrid/Operations/UpsertDataTest.php
new file mode 100644
index 0000000..cac2708
--- /dev/null
+++ b/tests/Hybrid/Operations/UpsertDataTest.php
@@ -0,0 +1,43 @@
+namespace->upsertData(new DataUpsert(
+ id: '1',
+ data: 'The capital of Japan is Tokyo',
+ ));
+
+ $this->waitForIndex($this->namespace);
+
+ $info = $this->namespace->getNamespaceInfo();
+
+ $this->assertSame(1, $info->vectorCount);
+ }
+
+ public function test_upsert_many_data(): void
+ {
+ $this->namespace->upsertDataMany([
+ new DataUpsert(id: '1', data: 'The capital of Japan is Tokyo'),
+ new DataUpsert(id: '2', data: 'The capital of France is Paris'),
+ new DataUpsert(id: '3', data: 'The capital of Germany is Berlin'),
+ ]);
+
+ $this->waitForIndex($this->namespace);
+
+ $info = $this->namespace->getNamespaceInfo();
+
+ $this->assertSame(3, $info->vectorCount);
+ }
+}
diff --git a/tests/Hybrid/Operations/UpsertVectorTest.php b/tests/Hybrid/Operations/UpsertVectorTest.php
new file mode 100644
index 0000000..3914475
--- /dev/null
+++ b/tests/Hybrid/Operations/UpsertVectorTest.php
@@ -0,0 +1,89 @@
+namespace->upsert(new VectorUpsert(
+ id: '1',
+ vector: $this->generateVector(384),
+ sparseVector: new SparseVector(
+ indices: [1, 2, 3],
+ values: [5, 6, 7],
+ ),
+ ));
+
+ $this->waitForIndex($this->namespace);
+
+ $info = $this->namespace->getNamespaceInfo();
+
+ $this->assertSame(1, $info->vectorCount);
+ }
+
+ public function test_upsert_without_sparse_vector_throws_exception(): void
+ {
+ $this->expectException(OperationFailedException::class);
+ $this->expectExceptionMessage('This index requires sparse vectors');
+
+ $this->namespace->upsert(new VectorUpsert(
+ id: '1',
+ vector: $this->generateVector(384),
+ ));
+ }
+
+ public function test_upsert_without_vector_throws_exception(): void
+ {
+ $this->expectException(OperationFailedException::class);
+ $this->expectExceptionMessage('This index requires dense vectors');
+
+ $this->namespace->upsert(new VectorUpsert(
+ id: '1',
+ sparseVector: new SparseVector(
+ indices: [1, 2, 3],
+ values: [5, 6, 7],
+ ),
+ ));
+ }
+
+ public function test_upsert_many_vectors(): void
+ {
+ $this->namespace->upsertMany([
+ new VectorUpsert(
+ id: '1',
+ vector: $this->generateVector(384),
+ sparseVector: new SparseVector(
+ indices: [1, 2, 3],
+ values: [5, 6, 7],
+ ),
+ ),
+ new VectorUpsert(
+ id: '2',
+ vector: $this->generateVector(384),
+ sparseVector: new SparseVector(
+ indices: [4, 5, 6],
+ values: [8, 9, 10],
+ ),
+ ),
+ ]);
+
+ $this->waitForIndex($this->namespace);
+
+ $info = $this->namespace->getNamespaceInfo();
+
+ $this->assertSame(2, $info->vectorCount);
+ }
+}