diff --git a/src/Database/Adapter/SQL.php b/src/Database/Adapter/SQL.php index 7af02a4f7..c9c4970e4 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 "; @@ -3264,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; } @@ -3282,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 fcb421f15..7dce4d452 100644 --- a/src/Database/Database.php +++ b/src/Database/Database.php @@ -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 c0026b634..a99de4711 100644 --- a/tests/e2e/Adapter/Scopes/VectorTests.php +++ b/tests/e2e/Adapter/Scopes/VectorTests.php @@ -2624,4 +2624,134 @@ 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 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 */ + $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'); + } }