From 1a4151d241b51162fd502d10515038694f986245 Mon Sep 17 00:00:00 2001 From: ArnabChatterjee20k Date: Thu, 6 Nov 2025 10:35:01 +0530 Subject: [PATCH 1/2] fixed count, upsert methods for vector --- src/Database/Adapter/SQL.php | 28 ++++++++- src/Database/Database.php | 6 +- tests/e2e/Adapter/Scopes/VectorTests.php | 73 ++++++++++++++++++++++++ 3 files changed, 101 insertions(+), 6 deletions(-) diff --git a/src/Database/Adapter/SQL.php b/src/Database/Adapter/SQL.php index 7af02a4f7..db21347df 100644 --- a/src/Database/Adapter/SQL.php +++ b/src/Database/Adapter/SQL.php @@ -3188,7 +3188,18 @@ public function count(Document $collection, array $queries = [], ?int $max = nul $queries = array_map(fn ($query) => clone $query, $queries); - $conditions = $this->getSQLConditions($queries, $binds); + // Extract vector queries (used for ORDER BY) and keep non-vector for WHERE + $vectorQueries = []; + $otherQueries = []; + foreach ($queries as $query) { + if (in_array($query->getMethod(), Query::VECTOR_TYPES)) { + $vectorQueries[] = $query; + } else { + $otherQueries[] = $query; + } + } + + $conditions = $this->getSQLConditions($otherQueries, $binds); if (!empty($conditions)) { $where[] = $conditions; } @@ -3206,12 +3217,23 @@ public function count(Document $collection, array $queries = [], ?int $max = nul ? 'WHERE ' . \implode(' AND ', $where) : ''; + // Add vector distance calculations to ORDER BY (similarity-aware LIMIT) + $vectorOrders = []; + foreach ($vectorQueries as $query) { + $vectorOrder = $this->getVectorDistanceOrder($query, $binds, $alias); + if ($vectorOrder) { + $vectorOrders[] = $vectorOrder; + } + } + $sqlOrder = !empty($vectorOrders) ? 'ORDER BY ' . implode(', ', $vectorOrders) : ''; + $sql = " SELECT COUNT(1) as sum FROM ( SELECT 1 FROM {$this->getSQLTable($name)} AS {$this->quote($alias)} - {$sqlWhere} - {$limit} + {$sqlWhere} + {$sqlOrder} + {$limit} ) table_count "; diff --git a/src/Database/Database.php b/src/Database/Database.php index fcb421f15..0e5fcf84e 100644 --- a/src/Database/Database.php +++ b/src/Database/Database.php @@ -603,12 +603,12 @@ function (mixed $value) { return \json_encode(\array_map(\floatval(...), $value)); }, /** - * @param string|null $value + * @param mixed $value * @return array|null */ - function (?string $value) { + function (mixed $value) { if (is_null($value)) { - return null; + return; } if (!is_string($value)) { return $value; diff --git a/tests/e2e/Adapter/Scopes/VectorTests.php b/tests/e2e/Adapter/Scopes/VectorTests.php index c0026b634..543ece8b6 100644 --- a/tests/e2e/Adapter/Scopes/VectorTests.php +++ b/tests/e2e/Adapter/Scopes/VectorTests.php @@ -2624,4 +2624,77 @@ public function testVectorQueryInNestedQuery(): void // Cleanup $database->deleteCollection('vectorNested'); } + + public function testVectorQueryCount(): void + { + /** @var Database $database */ + $database = static::getDatabase(); + + if (!$database->getAdapter()->getSupportForVectors()) { + $this->expectNotToPerformAssertions(); + return; + } + + $database->createCollection('vectorCount'); + $database->createAttribute('vectorCount', 'embedding', Database::VAR_VECTOR, 3, true); + + $database->createDocument('vectorCount', new Document([ + '$permissions' => [ + Permission::read(Role::any()) + ], + 'embedding' => [1.0, 0.0, 0.0], + ])); + + $count = $database->count('vectorCount', [ + Query::vectorCosine('embedding', [1.0, 0.0, 0.0]), + ]); + + $this->assertEquals(1, $count); + + $database->deleteCollection('vectorCount'); + } + + public function testVetorUpsert(): void + { + /** @var Database $database */ + $database = static::getDatabase(); + + if (!$database->getAdapter()->getSupportForVectors()) { + $this->expectNotToPerformAssertions(); + return; + } + + $database->createCollection('vectorUpsert'); + $database->createAttribute('vectorUpsert', 'embedding', Database::VAR_VECTOR, 3, true); + + $insertedDoc = $database->upsertDocument('vectorUpsert', new Document([ + '$id' => 'vectorUpsert', + '$permissions' => [ + Permission::read(Role::any()), + Permission::update(Role::any()) + ], + 'embedding' => [1.0, 0.0, 0.0], + ])); + + $this->assertEquals([1.0, 0.0, 0.0], $insertedDoc->getAttribute('embedding')); + + $insertedDoc = $database->getDocument('vectorUpsert', 'vectorUpsert'); + $this->assertEquals([1.0, 0.0, 0.0], $insertedDoc->getAttribute('embedding')); + + $updatedDoc = $database->upsertDocument('vectorUpsert', new Document([ + '$id' => 'vectorUpsert', + '$permissions' => [ + Permission::read(Role::any()), + Permission::update(Role::any()) + ], + 'embedding' => [2.0, 0.0, 0.0], + ])); + + $this->assertEquals([2.0, 0.0, 0.0], $updatedDoc->getAttribute('embedding')); + + $updatedDoc = $database->getDocument('vectorUpsert', 'vectorUpsert'); + $this->assertEquals([2.0, 0.0, 0.0], $updatedDoc->getAttribute('embedding')); + + $database->deleteCollection('vectorUpsert'); + } } From d4e3876d5127ccf4d500c8f6464b92be7839cde4 Mon Sep 17 00:00:00 2001 From: ArnabChatterjee20k Date: Thu, 6 Nov 2025 11:28:34 +0530 Subject: [PATCH 2/2] updated upsert fix, added sum fix --- src/Database/Adapter/SQL.php | 24 +++++++++- src/Database/Database.php | 7 ++- tests/e2e/Adapter/Scopes/VectorTests.php | 57 ++++++++++++++++++++++++ 3 files changed, 83 insertions(+), 5 deletions(-) diff --git a/src/Database/Adapter/SQL.php b/src/Database/Adapter/SQL.php index db21347df..c9c4970e4 100644 --- a/src/Database/Adapter/SQL.php +++ b/src/Database/Adapter/SQL.php @@ -3286,7 +3286,18 @@ public function sum(Document $collection, string $attribute, array $queries = [] $queries = array_map(fn ($query) => clone $query, $queries); - $conditions = $this->getSQLConditions($queries, $binds); + // Extract vector queries (used for ORDER BY) and keep non-vector for WHERE + $vectorQueries = []; + $otherQueries = []; + foreach ($queries as $query) { + if (in_array($query->getMethod(), Query::VECTOR_TYPES)) { + $vectorQueries[] = $query; + } else { + $otherQueries[] = $query; + } + } + + $conditions = $this->getSQLConditions($otherQueries, $binds); if (!empty($conditions)) { $where[] = $conditions; } @@ -3304,11 +3315,22 @@ public function sum(Document $collection, string $attribute, array $queries = [] ? 'WHERE ' . \implode(' AND ', $where) : ''; + // Add vector distance calculations to ORDER BY (similarity-aware LIMIT) + $vectorOrders = []; + foreach ($vectorQueries as $query) { + $vectorOrder = $this->getVectorDistanceOrder($query, $binds, $alias); + if ($vectorOrder) { + $vectorOrders[] = $vectorOrder; + } + } + $sqlOrder = !empty($vectorOrders) ? 'ORDER BY ' . implode(', ', $vectorOrders) : ''; + $sql = " SELECT SUM({$this->quote($attribute)}) as sum FROM ( SELECT {$this->quote($attribute)} FROM {$this->getSQLTable($name)} AS {$this->quote($alias)} {$sqlWhere} + {$sqlOrder} {$limit} ) table_count "; diff --git a/src/Database/Database.php b/src/Database/Database.php index 0e5fcf84e..7dce4d452 100644 --- a/src/Database/Database.php +++ b/src/Database/Database.php @@ -603,12 +603,12 @@ function (mixed $value) { return \json_encode(\array_map(\floatval(...), $value)); }, /** - * @param mixed $value + * @param string|null $value * @return array|null */ - function (mixed $value) { + function (?string $value) { if (is_null($value)) { - return; + return null; } if (!is_string($value)) { return $value; @@ -6149,7 +6149,6 @@ public function upsertDocumentsWithIncrease( if (!$old->isEmpty()) { $old = $this->adapter->castingAfter($collection, $old); - $old = $this->decode($collection, $old); } try { diff --git a/tests/e2e/Adapter/Scopes/VectorTests.php b/tests/e2e/Adapter/Scopes/VectorTests.php index 543ece8b6..a99de4711 100644 --- a/tests/e2e/Adapter/Scopes/VectorTests.php +++ b/tests/e2e/Adapter/Scopes/VectorTests.php @@ -2654,6 +2654,63 @@ public function testVectorQueryCount(): void $database->deleteCollection('vectorCount'); } + public function testVectorQuerySum(): void + { + /** @var Database $database */ + $database = static::getDatabase(); + + if (!$database->getAdapter()->getSupportForVectors()) { + $this->expectNotToPerformAssertions(); + return; + } + + $database->createCollection('vectorSum'); + $database->createAttribute('vectorSum', 'embedding', Database::VAR_VECTOR, 3, true); + $database->createAttribute('vectorSum', 'value', Database::VAR_INTEGER, 0, true); + + // Create documents with different values + $database->createDocument('vectorSum', new Document([ + '$permissions' => [ + Permission::read(Role::any()) + ], + 'embedding' => [1.0, 0.0, 0.0], + 'value' => 10 + ])); + + $database->createDocument('vectorSum', new Document([ + '$permissions' => [ + Permission::read(Role::any()) + ], + 'embedding' => [0.0, 1.0, 0.0], + 'value' => 20 + ])); + + $database->createDocument('vectorSum', new Document([ + '$permissions' => [ + Permission::read(Role::any()) + ], + 'embedding' => [0.5, 0.5, 0.0], + 'value' => 30 + ])); + + // Test sum with vector query - should sum all matching documents + $sum = $database->sum('vectorSum', 'value', [ + Query::vectorCosine('embedding', [1.0, 0.0, 0.0]), + ]); + + $this->assertEquals(60, $sum); + + // Test sum with vector query and filter combined + $sum = $database->sum('vectorSum', 'value', [ + Query::vectorCosine('embedding', [1.0, 0.0, 0.0]), + Query::greaterThan('value', 15), + ]); + + $this->assertEquals(50, $sum); + + $database->deleteCollection('vectorSum'); + } + public function testVetorUpsert(): void { /** @var Database $database */