From acdefcbcc7683a01e76ea31726e0a3f0c6b1c247 Mon Sep 17 00:00:00 2001 From: harsh mahajan Date: Thu, 10 Sep 2026 18:09:53 +0530 Subject: [PATCH 1/2] fix: respect configured query values when loading relationships --- src/Database/Database.php | 10 +-- .../e2e/Adapter/Scopes/RelationshipTests.php | 89 +++++++++++++++++++ 2 files changed, 94 insertions(+), 5 deletions(-) diff --git a/src/Database/Database.php b/src/Database/Database.php index 4c3fab0550..26e7ec3dcb 100644 --- a/src/Database/Database.php +++ b/src/Database/Database.php @@ -5325,7 +5325,7 @@ private function populateOneToOneRelationshipsBatch(array $documents, Document $ $relatedDocuments = []; // Process in chunks to avoid exceeding query value limits - foreach (\array_chunk($uniqueRelatedIds, self::RELATION_QUERY_CHUNK_SIZE) as $chunk) { + foreach (\array_chunk($uniqueRelatedIds, \max(1, \min(self::RELATION_QUERY_CHUNK_SIZE, $this->maxQueryValues))) as $chunk) { $chunkDocs = $this->find($relatedCollection->getId(), [ Query::equal('$id', $chunk), Query::limit(PHP_INT_MAX), @@ -5417,7 +5417,7 @@ private function populateOneToManyRelationshipsBatch( $relatedDocuments = []; - foreach (\array_chunk($parentIds, self::RELATION_QUERY_CHUNK_SIZE) as $chunk) { + foreach (\array_chunk($parentIds, \max(1, \min(self::RELATION_QUERY_CHUNK_SIZE, $this->maxQueryValues))) as $chunk) { $chunkDocs = $this->find($relatedCollection->getId(), [ Query::equal($twoWayKey, $chunk), Query::limit(PHP_INT_MAX), @@ -5514,7 +5514,7 @@ private function populateManyToOneRelationshipsBatch( $relatedDocuments = []; - foreach (\array_chunk($childIds, self::RELATION_QUERY_CHUNK_SIZE) as $chunk) { + foreach (\array_chunk($childIds, \max(1, \min(self::RELATION_QUERY_CHUNK_SIZE, $this->maxQueryValues))) as $chunk) { $chunkDocs = $this->find($relatedCollection->getId(), [ Query::equal($twoWayKey, $chunk), Query::limit(PHP_INT_MAX), @@ -5593,7 +5593,7 @@ private function populateManyToManyRelationshipsBatch( $junctions = []; - foreach (\array_chunk($documentIds, self::RELATION_QUERY_CHUNK_SIZE) as $chunk) { + foreach (\array_chunk($documentIds, \max(1, \min(self::RELATION_QUERY_CHUNK_SIZE, $this->maxQueryValues))) as $chunk) { $chunkJunctions = $this->skipRelationships(fn () => $this->find($junction, [ Query::equal($twoWayKey, $chunk), Query::limit(PHP_INT_MAX) @@ -5623,7 +5623,7 @@ private function populateManyToManyRelationshipsBatch( $uniqueRelatedIds = array_unique($relatedIds); $foundRelated = []; - foreach (\array_chunk($uniqueRelatedIds, self::RELATION_QUERY_CHUNK_SIZE) as $chunk) { + foreach (\array_chunk($uniqueRelatedIds, \max(1, \min(self::RELATION_QUERY_CHUNK_SIZE, $this->maxQueryValues))) as $chunk) { $chunkDocs = $this->find($relatedCollection->getId(), [ Query::equal('$id', $chunk), Query::limit(PHP_INT_MAX), diff --git a/tests/e2e/Adapter/Scopes/RelationshipTests.php b/tests/e2e/Adapter/Scopes/RelationshipTests.php index 9182b8b8bd..266cfec9af 100644 --- a/tests/e2e/Adapter/Scopes/RelationshipTests.php +++ b/tests/e2e/Adapter/Scopes/RelationshipTests.php @@ -26,6 +26,95 @@ trait RelationshipTests use ManyToOneTests; use ManyToManyTests; + /** + * @return array + */ + public static function relationshipQueryValueLimitProvider(): array + { + $cases = []; + foreach ([Database::RELATION_ONE_TO_ONE, Database::RELATION_ONE_TO_MANY, Database::RELATION_MANY_TO_ONE, Database::RELATION_MANY_TO_MANY] as $type) { + foreach (['parents', 'children'] as $collection) { + $cases[$type . ' ' . $collection] = [$type, $collection, 3]; + } + } + + // One parent can exceed the value limit when fetching its related documents. + $cases['manyToMany related documents'] = [Database::RELATION_MANY_TO_MANY, 'parents', 1]; + + return $cases; + } + + /** + * @dataProvider relationshipQueryValueLimitProvider + */ + public function testRelationshipQueryValueLimit(string $type, string $collection, int $limit): void + { + $database = $this->getDatabase(); + + if (!$database->getAdapter()->getSupportForRelationships()) { + $this->expectNotToPerformAssertions(); + return; + } + + $parents = ID::unique(); + $children = ID::unique(); + $permissions = [ + Permission::read(Role::any()), + Permission::create(Role::any()), + Permission::update(Role::any()), + Permission::delete(Role::any()), + ]; + $database->createCollection($parents, permissions: $permissions); + $database->createCollection($children, permissions: $permissions); + $database->createRelationship($parents, $children, $type, true, 'children', 'parents'); + + foreach (['child1', 'child2', 'child3'] as $id) { + $database->createDocument($children, new Document(['$id' => $id])); + } + + for ($i = 1; $i <= 3; $i++) { + $related = match ($type) { + Database::RELATION_MANY_TO_MANY => ['child1', 'child2', 'child3'], + Database::RELATION_ONE_TO_MANY => ['child' . $i], + default => 'child' . $i, + }; + $database->createDocument($parents, new Document([ + '$id' => 'parent' . $i, + 'children' => $related, + ])); + } + + $max = $database->getMaxQueryValues(); + $database->setMaxQueryValues(2); + + try { + $documents = $database->find($collection === 'parents' ? $parents : $children, [Query::limit($limit)]); + + $this->assertCount($limit, $documents); + foreach ($documents as $document) { + $related = $document->getAttribute($collection === 'parents' ? 'children' : 'parents'); + $related = $related instanceof Document ? [$related] : $related; + $ids = \array_map(fn (Document $related) => $related->getId(), $related); + $prefix = $collection === 'parents' ? 'child' : 'parent'; + $expected = $type === Database::RELATION_MANY_TO_MANY + ? [$prefix . '1', $prefix . '2', $prefix . '3'] + : [$prefix . \substr($document->getId(), -1)]; + $this->assertEqualsCanonicalizing($expected, $ids); + } + + try { + $database->find($children, [Query::equal('$id', ['child1', 'child2', 'child3'])]); + $this->fail('Explicit queries must still respect the configured value limit.'); + } catch (QueryException $exception) { + $this->assertSame('Invalid query: Query on attribute has greater than 2 values: $id', $exception->getMessage()); + } + } finally { + $database->setMaxQueryValues($max); + $database->deleteCollection($parents); + $database->deleteCollection($children); + } + } + public function testZoo(): void { /** @var Database $database */ From 5e6ecebbbd38f6bd6819a4188f7598070dd3d923 Mon Sep 17 00:00:00 2001 From: harsh mahajan Date: Fri, 11 Sep 2026 01:38:50 +0530 Subject: [PATCH 2/2] refactor: use configured limit for relationship batches --- src/Database/Database.php | 11 +++++------ tests/e2e/Adapter/Scopes/RelationshipTests.php | 8 ++------ 2 files changed, 7 insertions(+), 12 deletions(-) diff --git a/src/Database/Database.php b/src/Database/Database.php index 26e7ec3dcb..3f9f92ca9a 100644 --- a/src/Database/Database.php +++ b/src/Database/Database.php @@ -146,7 +146,6 @@ class Database public const RELATION_SIDE_CHILD = 'child'; public const RELATION_MAX_DEPTH = 3; - public const RELATION_QUERY_CHUNK_SIZE = 5000; // Orders public const ORDER_ASC = 'ASC'; @@ -5325,7 +5324,7 @@ private function populateOneToOneRelationshipsBatch(array $documents, Document $ $relatedDocuments = []; // Process in chunks to avoid exceeding query value limits - foreach (\array_chunk($uniqueRelatedIds, \max(1, \min(self::RELATION_QUERY_CHUNK_SIZE, $this->maxQueryValues))) as $chunk) { + foreach (\array_chunk($uniqueRelatedIds, \max(1, $this->maxQueryValues)) as $chunk) { $chunkDocs = $this->find($relatedCollection->getId(), [ Query::equal('$id', $chunk), Query::limit(PHP_INT_MAX), @@ -5417,7 +5416,7 @@ private function populateOneToManyRelationshipsBatch( $relatedDocuments = []; - foreach (\array_chunk($parentIds, \max(1, \min(self::RELATION_QUERY_CHUNK_SIZE, $this->maxQueryValues))) as $chunk) { + foreach (\array_chunk($parentIds, \max(1, $this->maxQueryValues)) as $chunk) { $chunkDocs = $this->find($relatedCollection->getId(), [ Query::equal($twoWayKey, $chunk), Query::limit(PHP_INT_MAX), @@ -5514,7 +5513,7 @@ private function populateManyToOneRelationshipsBatch( $relatedDocuments = []; - foreach (\array_chunk($childIds, \max(1, \min(self::RELATION_QUERY_CHUNK_SIZE, $this->maxQueryValues))) as $chunk) { + foreach (\array_chunk($childIds, \max(1, $this->maxQueryValues)) as $chunk) { $chunkDocs = $this->find($relatedCollection->getId(), [ Query::equal($twoWayKey, $chunk), Query::limit(PHP_INT_MAX), @@ -5593,7 +5592,7 @@ private function populateManyToManyRelationshipsBatch( $junctions = []; - foreach (\array_chunk($documentIds, \max(1, \min(self::RELATION_QUERY_CHUNK_SIZE, $this->maxQueryValues))) as $chunk) { + foreach (\array_chunk($documentIds, \max(1, $this->maxQueryValues)) as $chunk) { $chunkJunctions = $this->skipRelationships(fn () => $this->find($junction, [ Query::equal($twoWayKey, $chunk), Query::limit(PHP_INT_MAX) @@ -5623,7 +5622,7 @@ private function populateManyToManyRelationshipsBatch( $uniqueRelatedIds = array_unique($relatedIds); $foundRelated = []; - foreach (\array_chunk($uniqueRelatedIds, \max(1, \min(self::RELATION_QUERY_CHUNK_SIZE, $this->maxQueryValues))) as $chunk) { + foreach (\array_chunk($uniqueRelatedIds, \max(1, $this->maxQueryValues)) as $chunk) { $chunkDocs = $this->find($relatedCollection->getId(), [ Query::equal('$id', $chunk), Query::limit(PHP_INT_MAX), diff --git a/tests/e2e/Adapter/Scopes/RelationshipTests.php b/tests/e2e/Adapter/Scopes/RelationshipTests.php index 266cfec9af..2b162b2384 100644 --- a/tests/e2e/Adapter/Scopes/RelationshipTests.php +++ b/tests/e2e/Adapter/Scopes/RelationshipTests.php @@ -102,12 +102,8 @@ public function testRelationshipQueryValueLimit(string $type, string $collection $this->assertEqualsCanonicalizing($expected, $ids); } - try { - $database->find($children, [Query::equal('$id', ['child1', 'child2', 'child3'])]); - $this->fail('Explicit queries must still respect the configured value limit.'); - } catch (QueryException $exception) { - $this->assertSame('Invalid query: Query on attribute has greater than 2 values: $id', $exception->getMessage()); - } + $this->expectException(QueryException::class); + $database->find($children, [Query::equal('$id', ['child1', 'child2', 'child3'])]); } finally { $database->setMaxQueryValues($max); $database->deleteCollection($parents);