From b932ab8b1192ea14cd04100b332dcaf7f1cceb23 Mon Sep 17 00:00:00 2001 From: Damodar Lohani Date: Tue, 4 Feb 2025 02:48:19 +0000 Subject: [PATCH 1/5] Feat: foreach --- src/Database/Database.php | 36 ++++++++++++++++++++++++++++++++++++ 1 file changed, 36 insertions(+) diff --git a/src/Database/Database.php b/src/Database/Database.php index 1ccea9ec8..33a53b3b4 100644 --- a/src/Database/Database.php +++ b/src/Database/Database.php @@ -5624,6 +5624,42 @@ public function find(string $collection, array $queries = [], string $forPermiss return $results; } + + public function foreach(string $collection, array $queries = [], callable $callback, string $forPermission = Database::PERMISSION_READ): void + { + $grouped = Query::groupByType($queries); + $limitExists = $grouped['limit'] !== null; + $limit = $grouped['limit'] ?? 25; + + $results = []; + $sum = $limit; + $latestDocument = null; + + while ($sum === $limit) { + $newQueries = $queries; + if ($latestDocument !== null) { + array_unshift($newQueries, Query::cursorAfter($latestDocument)); + } + if (!$limitExists) { + $newQueries[] = Query::limit($limit); + } + $results = $this->find($collection, $newQueries, $forPermission); + + if (empty($results)) { + return; + } + + $sum = count($results); + + foreach ($results as $document) { + if (is_callable($callback)) { + $callback($document); + } + } + + $latestDocument = $results[array_key_last($results)]; + } + } /** * @param string $collection * @param array $queries From 09cf5c1b1cb5532a55a184b75625b449422f6d0c Mon Sep 17 00:00:00 2001 From: Damodar Lohani Date: Tue, 4 Feb 2025 04:27:39 +0000 Subject: [PATCH 2/5] add test for foreach --- tests/e2e/Adapter/Base.php | 25 +++++++++++++++++++++++++ 1 file changed, 25 insertions(+) diff --git a/tests/e2e/Adapter/Base.php b/tests/e2e/Adapter/Base.php index 0191ea853..9555287b5 100644 --- a/tests/e2e/Adapter/Base.php +++ b/tests/e2e/Adapter/Base.php @@ -5017,6 +5017,31 @@ public function testFindSelect(): void } } + /** @depends testFind */ + public function testForeach(): void + { + /** + * Test, foreach goes through all the documents + */ + $documents = []; + static::getDatabase()->foreach('movies', [Query::limit(2)], function ($document) use (&$documents) { + $documents[] = $document; + }); + $this->assertEquals(6, count($documents)); + + /** + * Test, foreach with initial cursor + */ + + $first = $documents[0]; + $documents = []; + static::getDatabase()->foreach('movies', [Query::limit(2), Query::cursorAfter($first)], function ($document) use (&$documents) { + $documents[] = $document; + }); + $this->assertEquals(5, count($documents)); + + } + /** * @depends testFind */ From 00eb66392f1bb45d8a960d19c7681ed2bfcefcb8 Mon Sep 17 00:00:00 2001 From: Damodar Lohani Date: Tue, 4 Feb 2025 04:58:41 +0000 Subject: [PATCH 3/5] improve and test --- src/Database/Database.php | 14 ++++++++++++++ tests/e2e/Adapter/Base.php | 10 ++++++++++ 2 files changed, 24 insertions(+) diff --git a/src/Database/Database.php b/src/Database/Database.php index 33a53b3b4..98f7e9bf1 100644 --- a/src/Database/Database.php +++ b/src/Database/Database.php @@ -5630,6 +5630,15 @@ public function foreach(string $collection, array $queries = [], callable $callb $grouped = Query::groupByType($queries); $limitExists = $grouped['limit'] !== null; $limit = $grouped['limit'] ?? 25; + $offset = $grouped['offset']; + + $cursor = $grouped['cursor']; + $cursorDirection = $grouped['cursorDirection']; + + // Cursor before is not supported + if($cursor !== null && $cursorDirection === Database::CURSOR_BEFORE) { + throw new DatabaseException('Cursor ' . Database::CURSOR_AFTER . ' not supported in this method.'); + } $results = []; $sum = $limit; @@ -5638,6 +5647,11 @@ public function foreach(string $collection, array $queries = [], callable $callb while ($sum === $limit) { $newQueries = $queries; if ($latestDocument !== null) { + //reset offset and cursor as groupByType ignores same type query after first one is encountered + if($offset !== null) { + array_unshift($newQueries, Query::offset(0)); + } + array_unshift($newQueries, Query::cursorAfter($latestDocument)); } if (!$limitExists) { diff --git a/tests/e2e/Adapter/Base.php b/tests/e2e/Adapter/Base.php index 9555287b5..19294650c 100644 --- a/tests/e2e/Adapter/Base.php +++ b/tests/e2e/Adapter/Base.php @@ -5040,6 +5040,16 @@ public function testForeach(): void }); $this->assertEquals(5, count($documents)); + /** + * Test, foreach with initial offset + */ + + $documents = []; + static::getDatabase()->foreach('movies', [Query::limit(2), Query::offset(2)], function ($document) use (&$documents) { + $documents[] = $document; + }); + $this->assertEquals(4, count($documents)); + } /** From 2c4b32f1878286f91d334748ed40f90f945eae3c Mon Sep 17 00:00:00 2001 From: Damodar Lohani Date: Tue, 4 Feb 2025 05:01:59 +0000 Subject: [PATCH 4/5] fix exception and test for it --- src/Database/Database.php | 6 +++--- tests/e2e/Adapter/Base.php | 13 +++++++++++++ 2 files changed, 16 insertions(+), 3 deletions(-) diff --git a/src/Database/Database.php b/src/Database/Database.php index 98f7e9bf1..268e02d21 100644 --- a/src/Database/Database.php +++ b/src/Database/Database.php @@ -5636,8 +5636,8 @@ public function foreach(string $collection, array $queries = [], callable $callb $cursorDirection = $grouped['cursorDirection']; // Cursor before is not supported - if($cursor !== null && $cursorDirection === Database::CURSOR_BEFORE) { - throw new DatabaseException('Cursor ' . Database::CURSOR_AFTER . ' not supported in this method.'); + if ($cursor !== null && $cursorDirection === Database::CURSOR_BEFORE) { + throw new DatabaseException('Cursor ' . Database::CURSOR_BEFORE . ' not supported in this method.'); } $results = []; @@ -5648,7 +5648,7 @@ public function foreach(string $collection, array $queries = [], callable $callb $newQueries = $queries; if ($latestDocument !== null) { //reset offset and cursor as groupByType ignores same type query after first one is encountered - if($offset !== null) { + if ($offset !== null) { array_unshift($newQueries, Query::offset(0)); } diff --git a/tests/e2e/Adapter/Base.php b/tests/e2e/Adapter/Base.php index 19294650c..f7b76cbda 100644 --- a/tests/e2e/Adapter/Base.php +++ b/tests/e2e/Adapter/Base.php @@ -5050,6 +5050,19 @@ public function testForeach(): void }); $this->assertEquals(4, count($documents)); + /** + * Test, cursor before throws error + */ + try { + static::getDatabase()->foreach('movies', [Query::cursorBefore($documents[1]), Query::offset(2)], function ($document) use (&$documents) { + $documents[] = $document; + }); + + } catch (Throwable $e) { + $this->assertInstanceOf(DatabaseException::class, $e); + $this->assertEquals('Cursor ' . Database::CURSOR_BEFORE . ' not supported in this method.', $e->getMessage()); + } + } /** From 5fdf290f8f336fbe98fb8c0846c446ad17c42690 Mon Sep 17 00:00:00 2001 From: Damodar Lohani Date: Tue, 4 Feb 2025 05:20:48 +0000 Subject: [PATCH 5/5] fix php stan errors --- src/Database/Database.php | 14 ++++++++++++-- tests/e2e/Adapter/Base.php | 8 ++++---- 2 files changed, 16 insertions(+), 6 deletions(-) diff --git a/src/Database/Database.php b/src/Database/Database.php index 268e02d21..0c33cd96b 100644 --- a/src/Database/Database.php +++ b/src/Database/Database.php @@ -5624,8 +5624,18 @@ public function find(string $collection, array $queries = [], string $forPermiss return $results; } - - public function foreach(string $collection, array $queries = [], callable $callback, string $forPermission = Database::PERMISSION_READ): void + /** + * Call callback for each document of the given collection + * that matches the given queries + * + * @param string $collection + * @param callable $callback + * @param array $queries + * @param string $forPermission + * @throws \Utopia\Database\Exception + * @return void + */ + public function foreach(string $collection, callable $callback, array $queries = [], string $forPermission = Database::PERMISSION_READ): void { $grouped = Query::groupByType($queries); $limitExists = $grouped['limit'] !== null; diff --git a/tests/e2e/Adapter/Base.php b/tests/e2e/Adapter/Base.php index f7b76cbda..945376ecb 100644 --- a/tests/e2e/Adapter/Base.php +++ b/tests/e2e/Adapter/Base.php @@ -5024,7 +5024,7 @@ public function testForeach(): void * Test, foreach goes through all the documents */ $documents = []; - static::getDatabase()->foreach('movies', [Query::limit(2)], function ($document) use (&$documents) { + static::getDatabase()->foreach('movies', queries: [Query::limit(2)], callback: function ($document) use (&$documents) { $documents[] = $document; }); $this->assertEquals(6, count($documents)); @@ -5035,7 +5035,7 @@ public function testForeach(): void $first = $documents[0]; $documents = []; - static::getDatabase()->foreach('movies', [Query::limit(2), Query::cursorAfter($first)], function ($document) use (&$documents) { + static::getDatabase()->foreach('movies', queries: [Query::limit(2), Query::cursorAfter($first)], callback: function ($document) use (&$documents) { $documents[] = $document; }); $this->assertEquals(5, count($documents)); @@ -5045,7 +5045,7 @@ public function testForeach(): void */ $documents = []; - static::getDatabase()->foreach('movies', [Query::limit(2), Query::offset(2)], function ($document) use (&$documents) { + static::getDatabase()->foreach('movies', queries: [Query::limit(2), Query::offset(2)], callback: function ($document) use (&$documents) { $documents[] = $document; }); $this->assertEquals(4, count($documents)); @@ -5054,7 +5054,7 @@ public function testForeach(): void * Test, cursor before throws error */ try { - static::getDatabase()->foreach('movies', [Query::cursorBefore($documents[1]), Query::offset(2)], function ($document) use (&$documents) { + static::getDatabase()->foreach('movies', queries: [Query::cursorBefore($documents[0]), Query::offset(2)], callback: function ($document) use (&$documents) { $documents[] = $document; });