From 432b88d79d49ab20169b6fa2bc27e83c138930c6 Mon Sep 17 00:00:00 2001 From: Arman <407448+armanist@users.noreply.github.com> Date: Wed, 13 May 2026 11:35:02 +0400 Subject: [PATCH 1/3] [#514] Preserve SleekDB criteria state across paginator count --- CHANGELOG.md | 1 + .../Adapters/Sleekdb/Statements/Result.php | 11 +++---- .../Sleekdb/Statements/ResultSleekTest.php | 32 +++++++++++++++++++ 3 files changed, 38 insertions(+), 6 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 803989c3..215f7350 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -79,6 +79,7 @@ - PHP 8.4 forward compatibility: Added missing type hint to `SleekDbal::__get()` magic method - Fixed deprecated `E_STRICT` constant usage in test bootstrap - Fixed cURL error message assertions for cross-version compatibility +- Fixed SleekDB paginator query-state regression where `count()` could clear criteria before paginated data fetch on the same model instance (#514) ### Added - `AppContext` class representing the runtime identity of a single application execution diff --git a/src/Database/Adapters/Sleekdb/Statements/Result.php b/src/Database/Adapters/Sleekdb/Statements/Result.php index 60866004..7c3fa9ee 100644 --- a/src/Database/Adapters/Sleekdb/Statements/Result.php +++ b/src/Database/Adapters/Sleekdb/Statements/Result.php @@ -128,12 +128,11 @@ public function first(): DbalInterface */ public function count(): int { - try { - $results = $this->fetchFilteredResultsFromBuilder($this->getBuilder()); - return count($results); - } finally { - $this->resetBuilderState(); - } + $counter = clone $this; + $counter->queryBuilder = null; + $counter->builderPrepared = false; + + return count($counter->fetchFilteredResultsFromBuilder($counter->getBuilder())); } /** diff --git a/tests/Unit/Database/Adapters/Sleekdb/Statements/ResultSleekTest.php b/tests/Unit/Database/Adapters/Sleekdb/Statements/ResultSleekTest.php index bcf3f83f..81a3e443 100644 --- a/tests/Unit/Database/Adapters/Sleekdb/Statements/ResultSleekTest.php +++ b/tests/Unit/Database/Adapters/Sleekdb/Statements/ResultSleekTest.php @@ -3,6 +3,7 @@ namespace Quantum\Tests\Unit\Database\Adapters\Sleekdb\Statements; use Quantum\Tests\Unit\Database\Adapters\Sleekdb\SleekDbalTestCase; +use Quantum\Tests\_root\shared\Models\TestEventModel; use Quantum\Database\Adapters\Sleekdb\SleekDbal; class ResultSleekTest extends SleekDbalTestCase @@ -85,4 +86,35 @@ public function testSleekAsArray(): void $this->assertIsArray($user->asArray()); } + + public function testSleekCountDoesNotResetCriteriaState(): void + { + $eventsModel = new SleekDbal('events'); + + $eventsModel->criteria('country', '=', 'Ireland'); + + $this->assertEquals(3, $eventsModel->count()); + + $events = $eventsModel + ->orderBy('title', 'asc') + ->get(); + + $this->assertCount(3, $events); + $this->assertEquals('Design', $events[0]->prop('title')); + } + + public function testSleekPaginateRetainsCriteriaAfterCount(): void + { + $eventModel = model(TestEventModel::class); + + $page = $eventModel + ->criteria('country', '=', 'Ireland') + ->orderBy('title', 'asc') + ->paginate(2, 1) + ->data(); + + $this->assertCount(2, $page); + $this->assertEquals('Design', $page->first()->title); + $this->assertEquals('Film', $page->last()->title); + } } From bd2cf3b27a001a426ed42cfdd1af8260d2eb9e9c Mon Sep 17 00:00:00 2001 From: Arman <407448+armanist@users.noreply.github.com> Date: Wed, 13 May 2026 13:10:50 +0400 Subject: [PATCH 2/3] [#514] Isolate paginator count while preserving terminal count semantics --- .../Adapters/Sleekdb/Statements/Result.php | 11 ++++++----- src/Paginator/Adapters/ModelPaginator.php | 18 ++++++++++++++++-- .../Sleekdb/Statements/ResultSleekTest.php | 6 +++--- 3 files changed, 25 insertions(+), 10 deletions(-) diff --git a/src/Database/Adapters/Sleekdb/Statements/Result.php b/src/Database/Adapters/Sleekdb/Statements/Result.php index 7c3fa9ee..60866004 100644 --- a/src/Database/Adapters/Sleekdb/Statements/Result.php +++ b/src/Database/Adapters/Sleekdb/Statements/Result.php @@ -128,11 +128,12 @@ public function first(): DbalInterface */ public function count(): int { - $counter = clone $this; - $counter->queryBuilder = null; - $counter->builderPrepared = false; - - return count($counter->fetchFilteredResultsFromBuilder($counter->getBuilder())); + try { + $results = $this->fetchFilteredResultsFromBuilder($this->getBuilder()); + return count($results); + } finally { + $this->resetBuilderState(); + } } /** diff --git a/src/Paginator/Adapters/ModelPaginator.php b/src/Paginator/Adapters/ModelPaginator.php index b4bac85c..7071b35e 100644 --- a/src/Paginator/Adapters/ModelPaginator.php +++ b/src/Paginator/Adapters/ModelPaginator.php @@ -19,6 +19,7 @@ use Quantum\Paginator\Contracts\PaginatorInterface; use Quantum\Paginator\Traits\PaginatorTrait; use Quantum\App\Exceptions\BaseException; +use Quantum\Model\Exceptions\ModelException; use Quantum\Di\Exceptions\DiException; use Quantum\Model\ModelCollection; use Quantum\Model\DbModel; @@ -38,7 +39,7 @@ class ModelPaginator implements PaginatorInterface private DbModel $model; /** - * @throws DiException|ReflectionException + * @throws DiException|ReflectionException|ModelException|BaseException */ public function __construct(DbModel $model, int $perPage, int $page = 1) { @@ -46,7 +47,7 @@ public function __construct(DbModel $model, int $perPage, int $page = 1) $this->model = $model; $this->modelClass = $model->getModelName(); - $this->total = $model->count(); + $this->total = $this->getCountFromClonedModel($model); } /** @@ -99,4 +100,17 @@ public function lastItem(): ?Model return $data->last(); } + + /** + * Counts results on an isolated model/ORM clone so total calculation cannot mutate + * the live model query state later used by paginator data(). + * @throws ModelException|BaseException + */ + private function getCountFromClonedModel(DbModel $model): int + { + $countModel = clone $model; + $countModel->setOrmInstance(clone $model->getOrmInstance()); + + return $countModel->count(); + } } diff --git a/tests/Unit/Database/Adapters/Sleekdb/Statements/ResultSleekTest.php b/tests/Unit/Database/Adapters/Sleekdb/Statements/ResultSleekTest.php index 81a3e443..bf74add1 100644 --- a/tests/Unit/Database/Adapters/Sleekdb/Statements/ResultSleekTest.php +++ b/tests/Unit/Database/Adapters/Sleekdb/Statements/ResultSleekTest.php @@ -87,7 +87,7 @@ public function testSleekAsArray(): void $this->assertIsArray($user->asArray()); } - public function testSleekCountDoesNotResetCriteriaState(): void + public function testSleekCountResetsCriteriaState(): void { $eventsModel = new SleekDbal('events'); @@ -99,8 +99,8 @@ public function testSleekCountDoesNotResetCriteriaState(): void ->orderBy('title', 'asc') ->get(); - $this->assertCount(3, $events); - $this->assertEquals('Design', $events[0]->prop('title')); + $this->assertCount(7, $events); + $this->assertEquals('Art', $events[0]->prop('title')); } public function testSleekPaginateRetainsCriteriaAfterCount(): void From 65516e5280c1f140f549e3b9cf577dfa6667d5ae Mon Sep 17 00:00:00 2001 From: Arman <407448+armanist@users.noreply.github.com> Date: Wed, 13 May 2026 13:24:36 +0400 Subject: [PATCH 3/3] [#514] Cover grouped criteria persistence across paginator count and data --- .../Sleekdb/Statements/ResultSleekTest.php | 34 +++++++++++++++++++ 1 file changed, 34 insertions(+) diff --git a/tests/Unit/Database/Adapters/Sleekdb/Statements/ResultSleekTest.php b/tests/Unit/Database/Adapters/Sleekdb/Statements/ResultSleekTest.php index bf74add1..e1213390 100644 --- a/tests/Unit/Database/Adapters/Sleekdb/Statements/ResultSleekTest.php +++ b/tests/Unit/Database/Adapters/Sleekdb/Statements/ResultSleekTest.php @@ -117,4 +117,38 @@ public function testSleekPaginateRetainsCriteriaAfterCount(): void $this->assertEquals('Design', $page->first()->title); $this->assertEquals('Film', $page->last()->title); } + + public function testSleekGroupedCriteriasPersistAcrossPaginateCountAndData(): void + { + $baseline = model(TestEventModel::class) + ->criterias( + [ + ['title', '=', 'Dance'], + ['title', '=', 'Art'], + ], + ['country', '=', 'Ireland'] + ) + ->orderBy('title', 'asc') + ->get(); + + $paginator = model(TestEventModel::class) + ->criterias( + [ + ['title', '=', 'Dance'], + ['title', '=', 'Art'], + ], + ['country', '=', 'Ireland'] + ) + ->orderBy('title', 'asc') + ->paginate(10, 1); + + $page = $paginator->data(); + + $baselineTitles = array_map(fn ($event) => $event->title, iterator_to_array($baseline)); + $pageTitles = array_map(fn ($event) => $event->title, iterator_to_array($page)); + + $this->assertSame(count($baseline), $paginator->total()); + $this->assertCount(count($baseline), $page); + $this->assertSame($baselineTitles, $pageTitles); + } }